目前, react
的生态愈来愈丰富,像 flux
redux
react-router
已经被愈来愈多的使用,本文就 react-router
的内部实现进行分析。文章主要包含两大部分: 一是对 react-router
赖以依存的 history
进行研究;二是分析 react-router
是如何实现URL
与 UI
同步的。html
history 是一个独立的第三方js库,能够用来兼容在不一样浏览器、不一样环境下对历史记录的管理,拥有统一的API。具体来讲里面的history分为三类:html5
createHashHistory
createBrowserHistory
createMemoryHistory
上面针对不一样的环境提供了三个API,可是三个API有一些共性的操做,将其抽象了一个公共的文件 createHistory
:node
// 内部的抽象实现 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
的操做。react
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的大体的技术实现以下:json
createBrowserHistory
: 利用HTML5里面的historycreateHashHistory
: 经过hash来存储在不一样状态下的history信息createMemoryHistory
: 在内存中进行历史记录的存储createBrowserHistory
: pushState、replaceStatecreateHashHistory
: location.hash=***
location.replace()
createMemoryHistory
: 在内存中进行历史记录的存储伪代码实现以下:redux
// 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层面的事情,因此能够直接进行历史信息的回退伪代码实现以下:api
// 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的同步,分为两个层次来描述具体的实现。session
在 react-router
中最主要的 component
是 Router
RouterContext
Link
, history
库起到了中间桥梁的做用。
为了简单说明,只描述使用browserHistory的实现,hashHistory的实现过程是相似的,就不在说明。
目前 react-router
在项目中已有大量实践,其优势能够总结以下:
<Link>
Redirect
进行路由的切换固然 react-router
的缺点就是API不太稳定,在升级版本的时候须要进行代码变更。