react-router 源码浅析
用 react-router 也用了比較久了,對他的內(nèi)部工作方式卻只是了解皮毛,而且大部分還是通過別人的博客。最近兩周打算自己探究一下他的實(shí)現(xiàn)。 注意!因?yàn)槲抑皇褂眠^ v3 版本的 react-router,因?yàn)閷λ氖褂梅绞奖容^熟悉,所以這次解析也是基于這個(gè)版本。
文章目錄:
- react-router 工作模式簡化流程
- 內(nèi)部具體實(shí)現(xiàn)
- Link 組件的實(shí)現(xiàn)方式 以及 *History.push 的實(shí)現(xiàn)方式
- 為什么要這樣實(shí)現(xiàn)?有什么別的方式?做個(gè)比較?
react-router 工作模式簡化流程
聊到這個(gè)話題就離不開前端路由。關(guān)于前端路由的一些演變過程和現(xiàn)有的方式可以看這篇文章。前端路由的重點(diǎn)就是不刷新頁面,現(xiàn)有的解決方案有 hashChange 和 popState 兩種。 React 提供API也是圍繞這兩種方式。 共同點(diǎn)都是發(fā)布訂閱的模式,讓瀏覽器事件觸發(fā)的時(shí)候自己添加的 listener 被調(diào)用。Router 組件包裹著 Route 組件,Route 組件負(fù)責(zé)描述每條路由的展示組件和匹配的路徑。這樣 Router 組件實(shí)際上會格式化出一個(gè)映射的路由表。
然而這是在頁面路由更新的時(shí)候,最開始進(jìn)入頁面的時(shí)候怎么辦呢?其實(shí)剛進(jìn)入頁面的時(shí)候也會進(jìn)行一次匹配,詳細(xì)分析見下一部分。
內(nèi)部具體實(shí)現(xiàn)
首先解答上面的遺留問題,剛進(jìn)入頁面的狀態(tài)如何帶入?這個(gè)問題我們可以和 "Router 組件是在什么時(shí)候添加的事件監(jiān)聽"放在一起解答。
componentWillMount() {//來源:modules/Router.jsthis.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 本身就會觸發(fā)一次匹配路由展示的過程。匹配的過程有 match 方法,用于各種嵌套路由的匹配。
但是注意,如果使用的是 browserHistory,這種路由方式一般是/a/b 這種方式,可能需要后端同學(xué)的配合。
封裝 history ——transitionManager
在上面的代碼中,我們會注意到添加監(jiān)聽器的 listen方法來自于 transitionManager 這個(gè)生成之后被賦值到 this.router 實(shí)例的屬性。實(shí)際上 react-router 的事件監(jiān)聽過程是用 transitionManager 套了 history 這個(gè)庫,抹平各種前端路由方式的調(diào)用差異。history庫本身暴露了一些API 比如監(jiān)聽、取消監(jiān)聽、跳轉(zhuǎn)等一系列方法。有基于咱們剛才提到的 hash 和 state 兩種方式。我們傳給 Router 組件 history 屬性的值其實(shí)就是他的實(shí)例。(拿hashHistory 舉栗,下面的文件是 reate-router export 的 hashHistory 的來源,也就是我們用的 hashHistory 的來源)。
//來源:modules/hashHistory.js import createHashHistory from 'history/lib/createHashHistory' import createRouterHistory from './createRouterHistory' export default createRouterHistory(createHashHistory)而 transitionManager 做的事情是針對當(dāng)前的 router 實(shí)例和開發(fā)者指定的 history 對象,對 history 庫給的 API 做一次二次封裝,加上修改路由狀態(tài)等等操作。然后開發(fā)者拿著 transitionManager 封裝之后暴露出的 listen 等方法操作路由。
createTransitionManager() {//來源:modules/Router.jsconst { matchContext } = this.propsif (matchContext) {return matchContext.transitionManager}const { history } = this.propsconst { routes, children } = this.propsinvariant(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(//注意這個(gè)createTransitionManager才是history,createRoutes(routes || children))},渲染部分
渲染過程不是放在 Route 組件中負(fù)責(zé)渲染,而是把狀態(tài)都放在 Router 中保存,詳細(xì)可見第一部分的代碼添加 listener 的部分。
assignRouterState(this.router, state)this.setState(state, this.props.onUpdate)而 Router 組件的 render 是這樣寫的:
const { location, routes, params, components } = this.stateconst { createElement, render, ...props } = this.propsreturn render({...props,router: this.router,location,routes,params,components,createElement})而 props 的值是當(dāng)前 Router 組件的狀態(tài),他現(xiàn)在要展示的組件,對應(yīng)的地址,當(dāng)前跳轉(zhuǎn)攜帶的參數(shù) params 等等。下面是調(diào)用 render 的部分。
return <RouterContext {...props} />RouterContext 包裝組件的主要作用就是把 props參數(shù)中存有當(dāng)前路由狀態(tài)的對象router存到全局。類似于 Redux 的 Provider 組件。
Link 組件的實(shí)現(xiàn)方式
這里小伙伴們可以猜測一下,Link是怎么做的呢?
我們知道 Link最后渲染完是個(gè) a 標(biāo)簽,我們通常會給 Link 組件幾個(gè)參數(shù),最常用的是跳轉(zhuǎn)的路由地址和攜帶的參數(shù)。通過上面的講解不難猜出,Link 在點(diǎn)擊的時(shí)候應(yīng)該是調(diào)用了一個(gè)跳轉(zhuǎn)的操作(八成也是 history 庫里給的),然后禁止掉默認(rèn)跳轉(zhuǎn)就行了。
事實(shí)也是如此,history 暴露了一個(gè) push方法,來 push 進(jìn)瀏覽器的歷史訪問棧中。 這里再提一句另外一種用法:*history.push() 的方式。這種其實(shí)就相當(dāng)于直接點(diǎn)擊了 a 標(biāo)簽一樣的道理,只不過用 js 的方式實(shí)現(xiàn)了。
為什么要這樣實(shí)現(xiàn)?有什么別的方式?做個(gè)比較?
我們可不可以嘗試把展示交給 route 組件管理?router 只控制激活當(dāng)前的 route?但是這樣就不能支持通過 props 傳給 router 路由配置的方式使用了,這是其一;
其二,這樣其實(shí) route 其實(shí)負(fù)責(zé)了組件的渲染工作,而不是把所有的狀態(tài)和路由信息全部放在 router 中管理了,不方便集中維護(hù)和擴(kuò)展。 不知道小伙伴們還有別的看法嗎?
(本來想寫個(gè)淺析的……噼里啪啦寫了一大堆……還捎帶點(diǎn)語無倫次……)
(但是雖然說了一堆……不過確實(shí)挺淺的……各位有興趣可以嘗試自己扒一下源碼。建議 react-router 和 history 庫一起 debug,更有助于我們?nèi)跁炌?#xff09;
( 各位見笑了)
總結(jié)
以上是生活随笔為你收集整理的react-router 源码浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己写一个H5项目CI系统
- 下一篇: 2018移动端页面适配-自适应最新方案直