react-router的实现原理

目前,react的生态越来越丰富,像flux redux react-router已经被越来越多的使用,本文就react-router的内部实现进行分析。文章主要包含两大部分: 一是对react-router赖以依存的history进行研究;二是分析react-router是如何实现URLUI同步的。

1. react-router依赖基础 - history

1.1 History整体介绍

history是一个独立的第三方js库,可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。具体来说里面的history分为三类:

  • 老浏览器的history: 主要通过hash来实现,对应createHashHistory

  • 高版本浏览器: 通过html5里面的history,对应createBrowserHistory

  • node环境下: 主要存储在memeory里面,对应createMemoryHistory

上面针对不同的环境提供了三个API,但是三个API有一些共性的操作,将其抽象了一个公共的文件createHistory:

 
  1. // 内部的抽象实现

  2. function createHistory(options={}) {

  3. ...

  4. return {

  5. listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现

  6. listen, // location发生改变时触发回调

  7. transitionTo, // 执行location的改变

  8. push, // 改变location

  9. replace,

  10. go,

  11. goBack,

  12. goForward,

  13. createKey, // 创建location的key,用于唯一标示该location,是随机生成的

  14. createPath,

  15. createHref,

  16. createLocation, // 创建location

  17. }

  18. }

  19.  

上述这些方式是history内部最基础的方法,createHashHistorycreateBrowserHistorycreateMemoryHistory只是覆盖其中的某些方法而已。其中需要注意的是,此时的location跟浏览器原生的location是不相同的,最大的区别就在于里面多了key字段,history内部通过key来进行location的操作。

 
  1. function createLocation() {

  2. return {

  3. pathname, // url的基本路径

  4. search, // 查询字段

  5. hash, // url中的hash值

  6. state, // url对应的state字段

  7. action, // 分为 push、replace、pop三种

  8. key // 生成方法为: Math.random().toString(36).substr(2, length)

  9. }

  10. }

1.2 内部解析

三个API的大致的技术实现如下:

  • createBrowserHistory: 利用HTML5里面的history

  • createHashHistory: 通过hash来存储在不同状态下的history信息

  • createMemoryHistory: 在内存中进行历史记录的存储

1.2.1 执行URL前进

  • createBrowserHistory: pushState、replaceState

  • createHashHistorylocation.hash=*** location.replace()

  • createMemoryHistory: 在内存中进行历史记录的存储

伪代码实现如下:

 
  1. // createBrowserHistory(HTML5)中的前进实现

  2. function finishTransition(location) {

  3. ...

  4. const historyState = { key };

  5. ...

  6. if (location.action === 'PUSH') ) {

  7. window.history.pushState(historyState, null, path);

  8. } else {

  9. window.history.replaceState(historyState, null, path)

  10. }

  11. }

  12. // createHashHistory的内部实现

  13. function finishTransition(location) {

  14. ...

  15. if (location.action === 'PUSH') ) {

  16. window.location.hash = path;

  17. } else {

  18. window.location.replace(

  19. window.location.pathname + window.location.search + '#' + path

  20. );

  21. }

  22. }

  23. // createMemoryHistory的内部实现

  24. entries = [];

  25. function finishTransition(location) {

  26. ...

  27. switch (location.action) {

  28. case 'PUSH':

  29. entries.push(location);

  30. break;

  31. case 'REPLACE':

  32. entries[current] = location;

  33. break;

  34. }

  35. }

1.2.2 检测URL回退

  • createBrowserHistorypopstate

  • createHashHistoryhashchange

  • createMemoryHistory: 因为是在内存中操作,跟浏览器没有关系,不涉及UI层面的事情,所以可以直接进行历史信息的回退

伪代码实现如下:

 
  1. // createBrowserHistory(HTML5)中的后退检测

  2. function startPopStateListener({ transitionTo }) {

  3. function popStateListener(event) {

  4. ...

  5. transitionTo( getCurrentLocation(event.state) );

  6. }

  7. addEventListener(window, 'popstate', popStateListener);

  8. ...

  9. }

  10.  
  11. // createHashHistory的后退检测

  12. function startPopStateListener({ transitionTo }) {

  13. function hashChangeListener(event) {

  14. ...

  15. transitionTo( getCurrentLocation(event.state) );

  16. }

  17. addEventListener(window, 'hashchange', hashChangeListener);

  18. ...

  19. }

  20. // createMemoryHistory的内部实现

  21. function go(n) {

  22. if (n) {

  23. ...

  24. current += n;

  25. const currentLocation = getCurrentLocation();

  26. // change action to POP

  27. history.transitionTo({ ...currentLocation, action: POP });

  28. }

  29. }

  30.  

1.2.3 state的存储

为了维护state的状态,将其存储在sessionStorage里面:

 
  1. // createBrowserHistory/createHashHistory中state的存储

  2. function saveState(key, state) {

  3. ...

  4. window.sessionStorage.setItem(createKey(key), JSON.stringify(state));

  5. }

  6. function readState(key) {

  7. ...

  8. json = window.sessionStorage.getItem(createKey(key));

  9. return JSON.parse(json);

  10. }

  11. // createMemoryHistory仅仅在内存中,所以操作比较简单

  12. const storage = createStateStorage(entries); // storage = {entry.key: entry.state}

  13.  
  14. function saveState(key, state) {

  15. storage[key] = state

  16. }

  17. function readState(key) {

  18. return storage[key]

  19. }

2. react-router的基本原理

一句话:实现URL与UI界面的同步。其中在react-router中,URL对应Location对象,而UI是由react components来决定的,这样就转变成locationcomponents之间的同步问题。

3. react-router具体实现

react-router在history库的基础上,实现了URL与UI的同步,分为两个层次来描述具体的实现。

3.1 组件层面描述实现过程

react-router中最主要的componentRouter RouterContext Linkhistory库起到了中间桥梁的作用。

3.2 API层面描述实现过程

为了简单说明,只描述使用browserHistory的实现,hashHistory的实现过程是类似的,就不在说明。

4. 结语

目前react-router在项目中已有大量实践,其优点可以总结如下:

  • 风格: 与React融为一体,专为react量身打造,编码风格与react保持一致,例如路由的配置可以通过component来实现

  • 简单: 不需要手工维护路由state,使代码变得简单

  • 强大: 强大的路由管理机制,体现在如下方面

    • 路由配置: 可以通过组件、配置对象来进行路由的配置

    • 路由切换: 可以通过<Link> Redirect进行路由的切换

    • 路由加载: 可以同步记载,也可以异步加载,这样就可以实现按需加载

  • 使用方式: 不仅可以在浏览器端的使用,而且可以在服务器端的使用

当然react-router的缺点就是API不太稳定,在升级版本的时候需要进行代码变动。

原文https://blog.csdn.net/tangzhl/article/details/79696055

猜你喜欢

转载自blog.csdn.net/sinat_17775997/article/details/88747069