上一篇react-router原理之Link跳转中提到了Link在onClick的处理函数中会调用history的push(或replace)方法。接下来咱们就以push方法为例来看一下history具体都作了些什么。Link中的history是经过context传入进来的,须要向外层进行查找,继续以官网为例,最外层是BrowserRouter。html
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const BasicExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
...
</ul>
<Route exact path="/" component={Home} />
...
</div>
</Router>
);
复制代码
打开BrowserRouter文件,能够看到声明了实例属性history对象,history对象的建立来自history包的createBrowserHistory方法。html5
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} />;
}
}
复制代码
createBrowserHistory(存储在modules/createBrowserHistory.js)最后返回一个history对象,history对象上拥有许多的属性和方法,其中就有push、replace、listen等。react
关于push方法核心代码就两行数组
globalHistory.pushState({ key, state }, null, href);
setState({ action, location });
复制代码
globalHistory对应的浏览器环境中的window.history对象,虽然window能够监听popstate事件,可是执行pushState或者replaceState是不会触发该事件的,只有点击浏览器的前进后退按钮时才会触发,所以调用pushState方法只是改变了地址栏的url,其余的没有任何变化。浏览器
为了达到url变化即从新渲染页面的目的,就须要用到setState方法了(这里的setState方法只是一个普通的函数)bash
setState方法中最关键的就是下面这一行代码,执行notifyListeners方法遍历listeners数组中的每一个listener并调用执行。服务器
transitionManager.notifyListeners(history.location, history.action);
// notifyListeners方法定义
let listeners = [];
const notifyListeners = (...args) => {
listeners.forEach(listener => listener(...args));
};
复制代码
若是把从新渲染页面的逻辑加入到listeners数组中,那么当点击Link的时候就能够实现页面更新的目的了。接下来就须要回到history生成的地方也就是BrowserHistory去找一找添加listener的逻辑,BrowserRouter在建立好history对象以后,经过props的形式把history传递给了Router。react-router
Router针对history作了两件事dom
componentWillMount() {
const { children, history } = this.props;
this.unlisten = history.listen(() => {
this.setState({
match: this.computeMatch(history.location.pathname)
});
});
}
复制代码
Router组件是Route的父组件,因此当Router从新render的时候,那么Route天然也能够触发render,这样就能够响应最新的url状态了。函数
html5也提供了history方法,为何react-router要用history包呢?
虽然history包的createBrowserHistory其实底层依赖的就是html5的history,不过history除了支持createBrowserHistory以外,还提供createHashHistory和createMemoryHistory,这三种方式底层依赖的基础技术各不相同,可是对外暴露的接口都是一致的。这其实就是history包的意义所在
history包对环境的差别进行抽象,提供统一的一致性接口,轻松实现了会话的管理
react-router是支持服务器端渲染的,因为在服务器环境中不存在html5的history对象,所以没法使用history包,因此也不能使用BrowserRouter。
针对服务器端环境,react-router提供了StaticRouter,StaticRouter与BrowserRouter最大的区别就体如今建立的history对象上面,二者的history对象拥有几乎彻底一致的属性方法。因为服务器环境没有history,所以也不会有history的改变,所以StaticRouter的history的方法(push、replace、go)等都是不可调用执行的。