前言
在介绍router之前,先描述一下什么是观察者模式。通俗得讲就是先注册(或者说是缓存)事件回调,然后当触发事件的时候,调用缓存的回调函数。比如history.js和redux.js其实就是这种模式。
前端路由用一句话表达就是:在不刷新浏览器页面的情况下,改变url的同时,更新视图。
react-router-dom本质上依赖history.js库,首先利用history.listen注册url变化事件的回调(也就是对应react 的 setState),然后当通过history.pushState来不刷新页面改变路由的时候,将自定义的最新的location作为参数,调用缓存的回调函数,来达到上面描述的目的。
BrowserRouter
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />
}
}
实例化history对象,传给Router组件。
**HashRouter
**
import { createHashHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />
}
}
实例化history对象,传给Router组件。
Router
class Router extends React.Component {
constructor() {
this.props.history.listen((location) => {
this.setState({ location });
})
}
}
Router组件只做了一件事情:通过history.listen,添加url变化的回调函数。回调函数中调用setState重置
state.location,进而重新执行render函数。这样就达到了路由变化,视图(render)执行的目的。
histort.createHashHistory
function createHashHistory() {
const listeners = []; // 缓存回调函数
window.addEventListener('popState', () => applyTx()); 当手动点击前进后退时,也触发回调函数
function applyTx() {
const location = getIndexAndLocation(); // 获取最新的location
// 注入最新的location,调用注册的回调。
// 其实就是调用Router组件注册的location => setState({ location })
listeners.forEach((callback) => { callback(location) })
}
function getIndexAndLocation() {
// 假设此时url为:host/path/#/home?id=1
// 则会将真正的hash作为pathname返回:{search: "?id=1", pathname: "/home"}
// 这样route组件才能将真正的hash跟props.pathname比较
return parsePath(window.lcoation.hash.substr(1));
}
function replace(to) {
let nextLocation = getNextLocation(to); // 根据to来自定义最新的location let url = getHistoryStateAndUrl(nextLocation); 根据nextLocation获取需要跳转的地址
window.history.replaceState(state, '', url); // 无刷新更新url
applyTx(); // url更新之后,循环listeners,调用事先注册的回调函数
}
return {
replace, // 触发url更新函数
listen(callback) { listeners.push(callback) } // 监听函数 ...others // 其它一些模块,就不展开说了
}
}
history.createBrowserHistory
createBrowerHistory跟createHashHistory的逻辑基本一样,只是在将路径解析成自定义的location的时候
略有不同,也就是getIndexAndLocation函数。hashHistroy是将真正的hash解析成pathname字段,而browerHistory
是直接将最新的location导出就好了。
function getIndexAndLocation() {
let { pathname, search, hash } = window.location;
return { pathname, search, hash }
}
Route
class Route extends React.Component {
render() {
const match = matchPath(location.pathname, this.props)
return (
match ? children : null
)
}
}
简单理解,route组件只做了一件事:每次render的时候,比较props.pathname和context注入的location.href是否匹配,
如果匹配则渲染children,否则返回null。
总结
history监听popstate/hashchange事件,同时包装了replaceState等事件。Router注册回调,回调中执行setState,这样就达到了路由变化,
回调事件执行,render重新渲染的目的。然后route.render中对href和props.pathname做匹配判断,来决定
是否渲染children。