react-router 源碼淺析

用 react-router 也用了比較久了,對他的內部工做方式卻只是瞭解皮毛,並且大部分仍是經過別人的博客。最近兩週打算本身探究一下他的實現。 注意!由於我只使用過 v3 版本的 react-router,由於對他的使用方式比較熟悉,因此此次解析也是基於這個版本。javascript

文章目錄:

  • react-router 工做模式簡化流程
  • 內部具體實現
  • Link 組件的實現方式 以及 *History.push 的實現方式
  • 爲何要這樣實現?有什麼別的方式?作個比較?

react-router 工做模式簡化流程

聊到這個話題就離不開前端路由。關於前端路由的一些演變過程和現有的方式能夠看這篇文章。前端路由的重點就是不刷新頁面,現有的解決方案有 hashChange 和 popState 兩種。 React 提供API也是圍繞這兩種方式。 共同點都是發佈訂閱的模式,讓瀏覽器事件觸發的時候本身添加的 listener 被調用。Router 組件包裹着 Route 組件,Route 組件負責描述每條路由的展現組件和匹配的路徑。這樣 Router 組件實際上會格式化出一個映射的路由表
然而這是在頁面路由更新的時候,最開始進入頁面的時候怎麼辦呢?其實剛進入頁面的時候也會進行一次匹配,詳細分析見下一部分。前端

內部具體實現

首先解答上面的遺留問題,剛進入頁面的狀態如何帶入?這個問題咱們能夠和 "Router 組件是在何時添加的事件監聽"放在一塊兒解答。java

componentWillMount() {//來源:modules/Router.js
    this.transitionManager = this.createTransitionManager()
    this.router = this.createRouterObject(this.state)

    this._unlisten = this.transitionManager.listen((error, state) => {
      if (error) {
        this.handleError(error)
      } else {
        // Keep the identity of this.router because of a caveat in ContextUtils:
        // they only work if the object identity is preserved.
        assignRouterState(this.router, state)
        this.setState(state, this.props.onUpdate)
      }
    })
  },
複製代碼

Router 組件在 willMount 生命週期添加了 listener,而添加 listener 自己就會觸發一次匹配路由展現的過程。匹配的過程有 match 方法,用於各類嵌套路由的匹配。
可是注意,若是使用的是 browserHistory,這種路由方式通常是/a/b 這種方式,可能須要後端同窗的配合。react

封裝 history ——transitionManager

在上面的代碼中,咱們會注意到添加監聽器的 listen方法來自於 transitionManager 這個生成以後被賦值到 this.router 實例的屬性。實際上 react-router 的事件監聽過程是用 transitionManager 套了 history 這個庫,抹平各類前端路由方式的調用差別。history庫自己暴露了一些API 好比監聽、取消監聽、跳轉等一系列方法。有基於我們剛纔提到的 hash 和 state 兩種方式。咱們傳給 Router 組件 history 屬性的值其實就是他的實例。(拿hashHistory 舉慄,下面的文件是 reate-router export 的 hashHistory 的來源,也就是咱們用的 hashHistory 的來源)。segmentfault

//來源:modules/hashHistory.js
import createHashHistory from 'history/lib/createHashHistory'
import createRouterHistory from './createRouterHistory'
export default createRouterHistory(createHashHistory)
複製代碼

而 transitionManager 作的事情是針對當前的 router 實例和開發者指定的 history 對象,對 history 庫給的 API 作一次二次封裝,加上修改路由狀態等等操做。而後開發者拿着 transitionManager 封裝以後暴露出的 listen 等方法操做路由。後端

createTransitionManager() {//來源:modules/Router.js
    const { matchContext } = this.props
    if (matchContext) {
      return matchContext.transitionManager
    }

    const { history } = this.props
    const { routes, children } = this.props

    invariant(
      history.getCurrentLocation,
      'You have provided a history object created with history v4.x or v2.x ' +
        'and earlier. This version of React Router is only compatible with v3 ' +
        'history objects. Please change to history v3.x.'
    )

    return createTransitionManager(//注意這個createTransitionManager纔是
      history,
      createRoutes(routes || children)
    )
  },
複製代碼
渲染部分

渲染過程不是放在 Route 組件中負責渲染,而是把狀態都放在 Router 中保存,詳細可見第一部分的代碼添加 listener 的部分。瀏覽器

assignRouterState(this.router, state)
     this.setState(state, this.props.onUpdate)
複製代碼

而 Router 組件的 render 是這樣寫的:bash

const { location, routes, params, components } = this.state
    const { createElement, render, ...props } = this.props

    return render({
      ...props,
      router: this.router,
      location,
      routes,
      params,
      components,
      createElement
    })
複製代碼

而 props 的值是當前 Router 組件的狀態,他如今要展現的組件,對應的地址,當前跳轉攜帶的參數 params 等等。下面是調用 render 的部分。react-router

return <RouterContext {...props} /> 複製代碼

RouterContext 包裝組件的主要做用就是把 props參數中存有當前路由狀態的對象router存到全局。相似於 Redux 的 Provider 組件。ide

Link 組件的實現方式

這裏小夥伴們能夠猜想一下,Link是怎麼作的呢?
咱們知道 Link最後渲染完是個 a 標籤,咱們一般會給 Link 組件幾個參數,最經常使用的是跳轉的路由地址和攜帶的參數。經過上面的講解不難猜出,Link 在點擊的時候應該是調用了一個跳轉的操做(八成也是 history 庫裏給的),而後禁止掉默認跳轉就好了。
事實也是如此,history 暴露了一個 push方法,來 push 進瀏覽器的歷史訪問棧中。 這裏再提一句另一種用法:*history.push() 的方式。這種其實就至關於直接點擊了 a 標籤同樣的道理,只不過用 js 的方式實現了。

爲何要這樣實現?有什麼別的方式?作個比較?

咱們可不能夠嘗試把展現交給 route 組件管理?router 只控制激活當前的 route?可是這樣就不能支持經過 props 傳給 router 路由配置的方式使用了,這是其一;
其二,這樣其實 route 其實負責了組件的渲染工做,而不是把全部的狀態和路由信息所有放在 router 中管理了,不方便集中維護和擴展。 不知道小夥伴們還有別的見解嗎?

(原本想寫個淺析的……噼裏啪啦寫了一大堆……還捎帶點語無倫次……) (可是雖說了一堆……不過確實挺淺的……各位有興趣能夠嘗試本身扒一下源碼。建議 react-router 和 history 庫一塊兒 debug,更有助於咱們融會貫通) ( 各位見笑了)

相關文章
相關標籤/搜索