react-router的原理

一、hash的方式

以 hash 形式(也可使用 History API 來處理)爲例,當 url 的 hash 發生變化時,觸發 hashchange 註冊的回調,回調中去進行不一樣的操做,進行不一樣的內容的展現html

function Router() {
    this.routes = {};
    this.currentUrl = '';
}
Router.prototype.route = function(path, callback) {
    this.routes[path] = callback || function(){};
};
Router.prototype.refresh = function() {
    this.currentUrl = location.hash.slice(1) || '/';
    this.routes[this.currentUrl]();
};
Router.prototype.init = function() {
    window.addEventListener('load', this.refresh.bind(this), false);
    window.addEventListener('hashchange', this.refresh.bind(this), false);
}
window.Router = new Router();
window.Router.init();

link的實現

能夠從 react-router/ Link 中看到,對該組件的點擊事件進行了阻止了瀏覽器的默認跳轉行爲,而改用 history 模塊的 pushState 方法去觸發 url 更新。html5

爲何React組件會更新

給history註冊了lisetner事件,也就是裏面的setState函數;node

history.push(pathname,state)函數執行,這裏會執行註冊的listener函數react

listerer中的React的setState會執行;redux

React組件更新;api

其實不管是react-router. react-redux. 可以使組件更新的根本緣由,仍是最後出發了setState函數;數組

對於react-router,實際上是對history原生對象的封裝,從新封裝了push函數,使得咱們在push函數執行的時候,能夠觸發在Router組件中組件裝載以前,執行了history.listener函數,該函數的主要做用就是給listeners數組添加監聽函數,每次執行history.push的時候,都會執行listenrs數組中添加的listener,這裏的listener就是傳入的箭頭函數,功能是執行了Router組件的setState函數,從《Router Switch Route源碼解析》文章中,能夠看出來,Router執行了setState以後,會將當前url地址欄對應的url傳遞下去,當Route組件匹配到該地址欄的時候,就會渲染該組件,若是匹配不到,Route組件就返回null;瀏覽器

componentWillMount() {
  const { children, history } = this.props

  invariant(
    children == null || React.Children.count(children) === 1,
    'A <Router> may have only one child element'
  )

  // Do this here so we can setState when a <Redirect> changes the
  // location in componentWillMount. This happens e.g. when doing
  // server rendering using a <StaticRouter>.
  //這裏執行history.listen()方法;傳入一個函數;箭頭函數的this指的是父級的做用域中的this值;
  this.unlisten = history.listen(() => {
    this.setState({
      match: this.computeMatch(history.location.pathname)
    })
  })
}

一、react-router依賴基礎 - historyreact-router

history是一個獨立的第三方js庫,能夠用來兼容在不一樣瀏覽器、不一樣環境下對歷史記錄的管理,擁有統一的API。具體來講裏面的history分爲三類:架構

  • 老瀏覽器的history: 主要經過hash來實現,對應createHashHistory
  • 高版本瀏覽器: 經過html5裏面的history,對應createBrowserHistory
  • node環境下: 主要存儲在memeory裏面,對應createMemoryHistory

  • createBrowserHistory: 利用HTML5裏面的history
  • createHashHistory: 經過hash來存儲在不一樣狀態下的history信息
  • createMemoryHistory: 在內存中進行歷史記錄的存儲

執行URL前進

  • createBrowserHistory: pushState、replaceState
  • createHashHistory: location.hash=*** location.replace()
  • createMemoryHistory: 在內存中進行歷史記錄的存儲

檢測URL回退

  • createBrowserHistory: popstate
  • createHashHistory: hashchange
  • createMemoryHistory: 由於是在內存中操做

二、react-router的基本原理

框架去攔截瀏覽器跳轉,本身去同步UI組件

其中在react-router中,URL對應Location對象,而UI是由react components來決定的,這樣就轉變成location與components之間的同步問題

image

經過router聲明瞭一份含有 path to component 的各個映射的路由表。
react-router 還提供的 Link 組件(以下),做爲提供更新 url 的途徑,觸發 Link 後最終將經過如上面定義的路由表進行匹配,並拿到對應的 component 及 state 進行 render 渲染頁面。

三、從點擊 Link 到 render 對應 component ,路由中發生了什麼

Router 在 react component 生命週期之組件被掛載前 componentWillMount 中使用 this.history.listen 去註冊了 url 更新的回調函數。回調函數將在 url 更新時觸發,回調中的 setState 起到 render 了新的 component 的做用。

Router.prototype.componentWillMount = function componentWillMount() {
    // .. 省略其餘
    var createHistory = this.props.history;
 
    this.history = _useRoutes2['default'](createHistory)({
      routes: _RouteUtils.createRoutes(routes || children),
      parseQueryString: parseQueryString,
      stringifyQuery: stringifyQuery
    });
 
    this._unlisten = this.history.listen(function (error, state) {
        _this.setState(state, _this.props.onUpdate);
    });
  };

上面的 _useRoutes2 對 history 操做即是對其作一層包裝,因此調用的 this.history 實際爲包裝之後的對象,該對象含有 _useRoutes2 中的 listen 方法,以下

function listen(listener) {
      return history.listen(function (location) {
          // .. 省略其餘
          match(location, function (error, redirectLocation, nextState) {
            listener(null, nextState);
          });
      });
}

可看到,上面代碼中,主要分爲兩部分

  1. 使用了 history 模塊的 listen 註冊了一個含有 setState 的回調函數(這樣就能使用 history 模塊中的機制)
  2. 回調中的 match 方法爲 react-router 所特有,match 函數根據當前 location 以及前面寫的 Route 路由表匹配出對應的路由子集獲得新的路由狀態值 state,具體實現可見 react-router/matchRoutes ,再根據 state 獲得對應的 component ,最終執行了 match 中的回調 listener(null, nextState) ,即執行了 Router 中的監聽回調(setState),從而更新了展現。
如何觸發監聽的回調函數的執行?

url 更新主要有兩種方式:簡單的 hash 更新或使用 history api 進行地址更新。在 react-router 中,其提供了 Link 組件,該組件能在 render 中使用,最終會表現爲 a 標籤,並將 Link 中的各個參數組合放它的 href 屬性中。能夠從 react-router/ Link 中看到,對該組件的點擊事件進行了阻止了瀏覽器的默認跳轉行爲,而改用 history 模塊的 pushState 方法去觸發 url 更新

能夠將以上 react-router 的整個包裝閉環總結爲
  1. 回調函數:含有可以更新 react UI 的 react setState 方法。
  2. 註冊回調:在 Router componentWillMount 中使用 history.listen 註冊的回調函數,最終放在 history 模塊的 回調函數數組 changeListeners 中。
  3. 觸發回調:Link 點擊觸發 history 中回調函數數組 changeListeners 的執行,從而觸發原來 listen 中的 setState 方法,更新了頁面

至於前進與後退的實現,是經過監聽 popstate 以及 hashchange 的事件,當前進或後退 url 更新時,觸發這兩個事件的回調函數,回調的執行方式 Link 大體相同,最終一樣更新了 UI ,這裏就再也不說明。

react-router 主要是利用底層 history 模塊的機制,經過結合 react 的架構機制作一層包裝.

router3的按需加載方式

const about = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../Component/about').default)
    },'about')
}

//配置route
<Route path="helpCenter" getComponent={about} />
相關文章
相關標籤/搜索