react-router原理之幕后history

上一篇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

  • 添加到context上,使得Link经过context便可得到history对象
  • 在componentWillMount中调用history.listen方法增长对url变动的监听,当url变化的时候调用setState触发Router的从新渲染
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状态了。函数

history包与html5 history的关系

html5也提供了history方法,为何react-router要用history包呢?

虽然history包的createBrowserHistory其实底层依赖的就是html5的history,不过history除了支持createBrowserHistory以外,还提供createHashHistory和createMemoryHistory,这三种方式底层依赖的基础技术各不相同,可是对外暴露的接口都是一致的。这其实就是history包的意义所在

history包对环境的差别进行抽象,提供统一的一致性接口,轻松实现了会话的管理

StaticRouter与BrowserRouter的区别

react-router是支持服务器端渲染的,因为在服务器环境中不存在html5的history对象,所以没法使用history包,因此也不能使用BrowserRouter。

针对服务器端环境,react-router提供了StaticRouter,StaticRouter与BrowserRouter最大的区别就体如今建立的history对象上面,二者的history对象拥有几乎彻底一致的属性方法。因为服务器环境没有history,所以也不会有history的改变,所以StaticRouter的history的方法(push、replace、go)等都是不可调用执行的。

相关文章
相关标签/搜索