我的博客地址: 點擊這裏html
目前,react
的生態愈來愈豐富,像flux
redux
react-router
已經被愈來愈多的使用,本文就react-router
的內部實現進行分析。文章主要包含兩大部分: 一是對react-router
賴以依存的history
進行研究;二是分析react-router
是如何實現URL
與UI
同步的。html5
history是一個獨立的第三方js庫,能夠用來兼容在不一樣瀏覽器、不一樣環境下對歷史記錄的管理,擁有統一的API。具體來講裏面的history分爲三類:node
老瀏覽器的history: 主要經過hash來實現,對應createHashHistory
react
高版本瀏覽器: 經過html5裏面的history,對應createBrowserHistory
git
node環境下: 主要存儲在memeory裏面,對應createMemoryHistory
github
上面針對不一樣的環境提供了三個API,可是三個API有一些共性的操做,將其抽象了一個公共的文件createHistory
:json
// 內部的抽象實現 function createHistory(options={}) { ... return { listenBefore, // 內部的hook機制,能夠在location發生變化前執行某些行爲,AOP的實現 listen, // location發生改變時觸發回調 transitionTo, // 執行location的改變 push, // 改變location replace, go, goBack, goForward, createKey, // 建立location的key,用於惟一標示該location,是隨機生成的 createPath, createHref, createLocation, // 建立location } }
上述這些方式是history內部最基礎的方法,createHashHistory
、createBrowserHistory
、createMemoryHistory
只是覆蓋其中的某些方法而已。其中須要注意的是,此時的location跟瀏覽器原生的location是不相同的,最大的區別就在於裏面多了key
字段,history
內部經過key
來進行location
的操做。redux
function createLocation() { return { pathname, // url的基本路徑 search, // 查詢字段 hash, // url中的hash值 state, // url對應的state字段 action, // 分爲 push、replace、pop三種 key // 生成方法爲: Math.random().toString(36).substr(2, length) } }
三個API的大體的技術實現以下:瀏覽器
createBrowserHistory
: 利用HTML5裏面的history服務器
createHashHistory
: 經過hash來存儲在不一樣狀態下的history信息
createMemoryHistory
: 在內存中進行歷史記錄的存儲
createBrowserHistory
: pushState、replaceState
createHashHistory
: location.hash=***
location.replace()
createMemoryHistory
: 在內存中進行歷史記錄的存儲
僞代碼實現以下:
// createBrowserHistory(HTML5)中的前進實現 function finishTransition(location) { ... const historyState = { key }; ... if (location.action === 'PUSH') ) { window.history.pushState(historyState, null, path); } else { window.history.replaceState(historyState, null, path) } } // createHashHistory的內部實現 function finishTransition(location) { ... if (location.action === 'PUSH') ) { window.location.hash = path; } else { window.location.replace( window.location.pathname + window.location.search + '#' + path ); } } // createMemoryHistory的內部實現 entries = []; function finishTransition(location) { ... switch (location.action) { case 'PUSH': entries.push(location); break; case 'REPLACE': entries[current] = location; break; } }
createBrowserHistory
: popstate
createHashHistory
: hashchange
createMemoryHistory
: 由於是在內存中操做,跟瀏覽器沒有關係,不涉及UI層面的事情,因此能夠直接進行歷史信息的回退
僞代碼實現以下:
// createBrowserHistory(HTML5)中的後退檢測 function startPopStateListener({ transitionTo }) { function popStateListener(event) { ... transitionTo( getCurrentLocation(event.state) ); } addEventListener(window, 'popstate', popStateListener); ... } // createHashHistory的後退檢測 function startPopStateListener({ transitionTo }) { function hashChangeListener(event) { ... transitionTo( getCurrentLocation(event.state) ); } addEventListener(window, 'hashchange', hashChangeListener); ... } // createMemoryHistory的內部實現 function go(n) { if (n) { ... current += n; const currentLocation = getCurrentLocation(); // change action to POP history.transitionTo({ ...currentLocation, action: POP }); } }
爲了維護state的狀態,將其存儲在sessionStorage裏面:
// createBrowserHistory/createHashHistory中state的存儲 function saveState(key, state) { ... window.sessionStorage.setItem(createKey(key), JSON.stringify(state)); } function readState(key) { ... json = window.sessionStorage.getItem(createKey(key)); return JSON.parse(json); } // createMemoryHistory僅僅在內存中,因此操做比較簡單 const storage = createStateStorage(entries); // storage = {entry.key: entry.state} function saveState(key, state) { storage[key] = state } function readState(key) { return storage[key] }
一句話:實現URL與UI界面的同步。其中在react-router中,URL
對應Location
對象,而UI是由react components
來決定的,這樣就轉變成location
與components
之間的同步問題。
react-router在history
庫的基礎上,實現了URL與UI的同步,分爲兩個層次來描述具體的實現。
在react-router
中最主要的component
是Router
RouterContext
Link
,history
庫起到了中間橋樑的做用。
爲了簡單說明,只描述使用browserHistory的實現,hashHistory的實現過程是相似的,就不在說明。
目前react-router
在項目中已有大量實踐,其優勢能夠總結以下:
風格: 與React融爲一體,專爲react量身打造,編碼風格與react保持一致,例如路由的配置能夠經過component來實現
簡單: 不須要手工維護路由state,使代碼變得簡單
強大: 強大的路由管理機制,體如今以下方面
路由配置: 能夠經過組件、配置對象來進行路由的配置
路由切換: 能夠經過<Link>
Redirect
進行路由的切換
路由加載: 能夠同步記載,也能夠異步加載,這樣就能夠實現按需加載
使用方式: 不只能夠在瀏覽器端的使用,並且能夠在服務器端的使用
固然react-router
的缺點就是API不太穩定,在升級版本的時候須要進行代碼變更。