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)等都是不可調用執行的。

相關文章
相關標籤/搜索