欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

理解React Router路由

程序员文章站 2022-03-02 08:21:11
...

React-Router的特性

路由的基本原理就是保证view和url同步,React-Router有下面一些特点

  • 声明式的路由 跟react一样,我们可以声明式的书写router,可以用JSX语法

  • 嵌套路由及路径匹配

  • 支持多种路由切换方式 可以用hashchange或者history.putState。

    hashChange的兼容性较好,但在浏览器地址栏显示#看上去会很丑;而且hash记录导航历史不支持location.key和location.state。hashHistory即hashChange的实现。

    history.putState可以给我们提供优雅的url,但需要额外的服务端配置解决路径刷新问题;browserHistory即history.pushState的实现。

    因为两种方式都有优缺点,我们可以根据自己的业务需求进行挑选,这也是为什么我们的路由配置中需要从react.router引入browserHistory并将其当作props传给Router。

    React Router的API:

    主要有

    • BrowserRouter

    • HashRouter

    • MemoryRouter

    • StaticRouter

    • Link

    • NavLink

    • Redirect

    • Prompt

    • Route

    • Router

    • Switch

      这些组件的具体用法可以在官网查看,(reacttraining.com/react-route…)(segmentfault.com/a/119000001…),这里对它们做个总结:

      BrowserRouter使用HTML5提供的History api(putState,replaceState和popState事件)来保持UI和URL的同步,而HashRouter使用URL的hash部分(即window.location.hash)来保持UI和URL的同步;HashRouter主要用于支持低版本的浏览器,因此对于一些新式浏览器,我们鼓励使用BrowserHistory。

      MemoryRouter将历史记录保存在内存中,这个在测试和非浏览器环境中很有用,例如react native。

      StaticRouter是一个永远不会改变位置的Router,这在服务端渲染场景中非常有用,因为用户实际上没有点击,所以位置时间上没有发生变化。

      NavLink与Link的区别主要在于,前者会在与URL匹配时为呈现要素添加样式属性。

      Route是这些组件中重要的组件,它的任务就是在其path属性与某个location匹配时呈现一些UI。

      对于Router,一般程序只会使用其中一个高阶Router,包括BrowserRouter,HashRouter,MemoryRouter,NativeRouter和StaticRouter;

      Switch用于渲染与路径匹配的第一个Route或Redirect。

React-Router的依赖基础history

1.基础概念

history是一个独立的第三方js库,可以用来兼容在不同浏览器、不同环境下对历史记录的管理。主要包括三类,

  • 老版本的history:通过hash来实现,对应createHashHistory
  • 高版本的history:通过HTML5里面的history,对应createBrowserHistory
  • node环境下:主要存储在memory里面,对应creatememoryHistory

上面针对不同的环境提供了三个API,但三个API有一些共性的操作,将其抽象了一个公共的文件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
  }
}
复制代码

其中需要注意的是,此时的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: 在内存中进行历史记录的存储
2.1 执行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;
  }
}
复制代码
2.2 检测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 });
  }
}
复制代码
2.3 存储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]
}
复制代码

从上面三个例子可以看出,三种api的实现方式是不一样的,具体的细节可以看源码。

React—Router的原理

React-Router是在history库基础上实现的。

![0e753ac7e440fca9cb1cd7995ce26ef0_b](C:\Users\cyw\Desktop\summary\理解React Router路由\图片\0e753ac7e440fca9cb1cd7995ce26ef0_b.png)

点击Link标签路由系统发生的变化

  1. Link组件最终会被渲染成HTML标签,它的to,query,hash属性会被组合在一起并渲染为href属性

然后调用window.location.hash或者window.history.putState()修改了应用的url,这取决于history对象的方式。同时会触发history.listen中注册的事件监听器。history包中底层的putState方法支持传入两个参数state和path,在函数体内有将这两个参数传输到createLocation方法中,返回location的结构如下:

location = {
  pathname, // 当前路径,即 Link 中的 to 属性
  search, // search
  hash, // hash
  state, // state 对象
  action, // location 类型,在点击 Link 时为 PUSH,浏览器前进后退时为 POP,调用 replaceState 方法时为 REPLACE
  key, // 用于操作 sessionStorage 存取 state 对象
};
复制代码

将上述location对象作为参数传入到Transition方法中,得到新的location对象

接下来就是系统修改UI了

在得到新的location对象后,系统内部的matchRoutes方法会匹配出Route组件树中与与当前location对象匹配的一个子集,并且得到了nextState

nextState = {
  location, // 当前的 location 对象
  routes, // 与 location 对象匹配的 Route 树的子集,是一个数组
  params, // 传入的 param,即 URL 中的参数
  components, // routes 中每个元素对应的组件,同样是数组
};
复制代码

在Router组件的componentWillMount生命周期方法中调用了history.listen(listener)方法;listener会在上述matchRoutes方法执行成功后执行listener(nextState),接下来执行this.setState(nextState)就可以实现重新渲染Router组件。

点击前进或后退发生的变化

可以简单把浏览器的历史记录比作成一个仅有入栈操作的栈,当用户浏览到某一个页面时将该文档存入到栈中,点击后退或前进按钮时移动指针到history栈中对应的某一个文档。

location.hash与hashChange事件

使用hashChange事件来监听window.location.hash的变化,路由系统会将所有的路由信息都保存到location.hash中 hash变化时浏览器会更新url,并且在history栈中产生一条记录 window.addListener('hashChange',listen,false)事件监听器可以通过hash fragment获取当前url对应的location对象 接下来的过程与点击Link组件时一致

history.putState与popState事件

在浏览器前进或者后退时触发popstate事件,然后注册window.addEventListener('popstate',listener,false),并且可以在事件对象中取出对应的state对象

state对象可以存储一些恢复该页面所需要的简单信息,上文已经提到state会作为属性存储在location对象中,我们可以通过location.state来获取

在react-router内部将该对象存储到sessionStorage中,即saveState操作

接下来与上述第一种方式一致

参考文章:

深入理解React-Router路由原理

React Router中文文档

React Router英文文档

React Router API介绍