如何实现react-router?实现原理分享!

2021-07-06 16:48:57 浏览数 (3327)

对于 react 框架大家并不陌生,由于互联网发展的趋势 react 的生态变得越来越丰富了,就拿​flux redux react-router​来说,现在使用的也比较多,那么我们就来聊聊“如何实现 react-router?”这个问题,下面是有关于实现原理的分享,希望对大家有所帮助。


一、react-router依赖基础-history

1、History整体介绍

对于history 来说,它是独立的第三方 js 库,是可以用来兼容在不同环境和浏览器下历史记录的管理,而且还拥有统一的 API,在 history中主要分为这几类:

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

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

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

对于这三个类,在不同的环境中还提供了三个PAI,而且他们有一些相同性质的操作,就是将公共文件 createHistory 抽象化,代码如下所示:

// 内部的抽象实现
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​的操作;代码如下所示:

function createLocation() {
  return {
    pathname, // url的基本路径
    search, // 查询字段
    hash, // url中的hash值
    state, // url对应的state字段
    action, // 分为 push、replace、pop三种
    key // 生成方法为: Math.random().toString(36).substr(2, length)
  }
}

    

2、 内部解析

对于我们提及到的三个 API 的实现方法如下:

  • createBrowserHistory: 利用 HTML5 里面的 history。

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

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

3、 执行URL前进

这三个方法在执行 URL 的方法如下所示:

  • 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;
  }
}

4、 检测URL回退

三个方法的使用方式如下所示:

  • 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 });
  }
}

5、  state的存储

对于 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]
}

二、react-router的基本原理


 我们先来看一张 react-router 原理图,如下所示:

react-router原理图

 


在这张流程中我们可以知道,在 react-router 中,URL对应Location对象,而UI是由 react components来决定的,这样就转变成locationcomponents之间的同步问题。


三、react-router具体实现    

通过上面的知识我们知道,react-router 在 history 这个库类的基础上实现URL和UI同步的话分为这另个层次来描述实现的具体步骤:

1、  组件层面描述实现过程 

我们先来看看下面这张流程图:

react-router的上层实现流程图

在这个流程图中我们知道最主要的 component 是Router RouterContext Link,对于history库的话只有起到了中间桥梁的作用。

2、API层面描述实现过程 

我们在 API 这个层次中我们可以通过下面这张流程图了解,如下所示:

API层面描述实现过程流程图


小结:

对于react-router来说目前是比较受欢迎的,而且也在很多的项目中被大量的使用,它的有点可以分为下面几点:

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

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

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

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

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

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

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

当然 react-router 并不是说都没有缺点,它的缺点就是 API 比较不太稳定。


总结:

对于“如何实现react-router?”这问题和相关的实现原理我们通过描述和流程图有了大致的了解,我们可以通过相对应的学习和教程练习来进行巩固,在这边小编为大家提供了一个好用可靠的平台 W3Cschool,大家可以在平台中进行更深度的学习和了解。当然如果你有其他更好的方法也可以和大家一同分享。