h5 history和前端路由

2019 / 06 / 30

很多东西说出口当时可能会觉得自己很有道理,振振有词,但是当积累了一定的知识之后,很多东西会刷新你的认知,当时说的很多话可能只是井底之蛙的谬言。

h5的historyAPI是一个非常振奋人心的功能。它让前端路由变得非常便利,它的最大的好处是把路由和页面的渲染彻底的拆分开来。这有什么意义呢,比如之前前端路由,通过ajax动态改变页面一直被人诟病的没有历史记录就能很好的通过它解决。

其实意义远不止于此,在常规思维中,前端是通过url来确定如何展示页面的,但是事实上,url到展示页面是一个非常不灵活的过程,甚至因为这一点导致前端的动态展示变得很吃力,展示时如果需要交互的话也很难实现,historyAPI的意义就是让浏览记录和页面展示分离开来,页面怎么炫酷的渲染都可以,只需要通过historyAPI去控制浏览记录即可。

history对象

window.history对象可以很好的操作浏览器的历史记录,提供了go, back, forward方法来控制浏览的页面。

pushState和replaceState

html5对路由做了很大的支持,window.history.pushState,这个方法会推一个浏览记录进去,浏览器的路由显示变了,但并不会更新页面,甚至不检查页面发生了什么。

这个东西如果直接用的话很难想到他有什么意义。其实这个方法是用来解决前端路由的问题的,当使用前端路由发生改变页面的时候,并不会真正的改变浏览记录,也就是说页面并不知道自己被改变了,而这个方法就是告诉浏览器页面被改变了,同步一下浏览记录。

这为前端渲染提供了巨大的便利,我们不再需要hash来记录页面的状态,也不会因为前端路由而损失真正的浏览体验。

popstate事件

浏览器的popstate事件用来监听浏览记录的前进倒退,但pushState和replaceState不会触发这个事件,可以用于当用户前进或者后退时的重新渲染或者改变状态。

简单的实现一下react-router-dom

定义context,react-router-dom使用了全局的state来保存当前的history和location,所以需要使用到contextAPI来创建一个公用的store。

import React from 'react'

export interface RouteComponentProps {
location: { pathname: string }
history: {
push: (pathname: string) => void
replace: (pathname: string) => void
}
}

interface RouteProps {
component: React.ComponentType
path: string
}

const pathname = window.location.pathname
const defaultRouterContext: RouteComponentProps = {
location: { pathname },
history: {
push: (pathname: string) => { },
replace: (pathname: string) => { }
}
}

const RouterContext = React.createContext(defaultRouterContext)

Router组件会提供全局的state和改变state的方法

export class Router extends React.Component {
state = {
location: { pathname }
}

componentDidMount() {
window.addEventListener('popstate', () => {
const pathname = window.location.pathname
this.handleChangeLocation(pathname)
})
}

handleChangeLocation = pathname => {
const location = this.state.location
location.pathname = pathname
this.setState({ location })
}

push = pathname => {
this.handleChangeLocation(pathname)
window.history.pushState({ type: 'page' }, 'change page', pathname)
}

replace = pathname => {
this.handleChangeLocation(pathname)
window.history.replaceState({ type: 'page' }, 'change page', pathname)
}

render() {
const { children } = this.props
const { location } = this.state
const history = { push: this.push, replace: this.replace }

return (
<RouterContext.Provider value={{ location, history }}>
{children}
</RouterContext.Provider>
)
}
}

Route组件匹配location中的state,如果匹配到就显示,否则不显示,并且把history注入到组件中去。

export class Route extends React.Component<RouteProps> {
render() {
const { component, path } = this.props
const MatchedComponent = component as unknown as React.ComponentType<RouteComponentProps>

return (
<RouterContext.Consumer>
{value => {
const isMatched = value.location.pathname === path

return isMatched
? <MatchedComponent location={value.location} history={value.history} />
: null
}}
</RouterContext.Consumer>
)
}
}

当然真正的react-router-dom的实现要远比这个复杂,它提供了大量的功能,包括重定向和各种匹配方法,这里简单的说一下思路。

嗨,请先 登录

加载中...
(๑>ω<๑) 又是元气满满的一天哟