前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【路由】:路由那些事——中

【路由】:路由那些事——中

作者头像
WEBJ2EE
发布2021-04-07 00:42:22
1.1K0
发布2021-04-07 00:42:22
举报
文章被收录于专栏:WebJ2EE

6. React Router 关键源码分析

6.1. 库结构

6.2. react-router 的关键依赖项?

6.3. path-to-regexp 是干什么的?

  • Turn an Express-style path string such as /user/:name into a regular expression.
  • <Route path={???}>:Any valid URL path or array of paths that path-to-regexp@^1.7.0 understands.

react-router 的路径识别、匹配就是靠的 path-to-regexp

示例:(特别注意,下面是基于"path-to-regexp": "^6.2.0" 搞的测试)

代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

/*
 * 参数(Parameters)
 * The path argument is used to define parameters and populate keys.
 */

// 具名参数
const p1 = pathToRegexp("/foo/:bar");
console.log(p1, p1.exec("/foo/xyz"));
console.log("-----------------------");

const p2 = pathToRegexp("/:foo/:bar");
console.log(p2, p2.exec("/test/route"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// 自定义参数
const p3 = pathToRegexp("/icon-:foo(\\d+).png");
console.log(p3, p3.exec("/icon-123.png"));
console.log(p3, p3.exec("/icon-abc.png"));
console.log("-----------------------");

const p4 = pathToRegexp("/(user|u)");
console.log(p4, p4.exec("/u"));
console.log(p4, p4.exec("/users"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// Parameters can be wrapped in {} 
// to create custom prefixes or suffixes for your segment:
const p5 = pathToRegexp("/:attr1?{-:attr2}?{-:attr3}?");
console.log(p5, p5.exec("/"));
console.log(p5, p5.exec("/test"));
console.log(p5, p5.exec("/test-test"));
console.log("-----------------------");

const p6 = pathToRegexp("/:attr1{-:attr2}{-:attr3}");
console.log(p5, p5.exec("/test1-test2-test3"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// 无名参数
// It is possible to write an unnamed parameter 
// that only consists of a regexp. 
// It works the same the named parameter, 
// except it will be numerically indexed:
const p7 = pathToRegexp("/:foo/(.*)");
console.log(p7, p7.exec("/test/route"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

/*
 * 修饰符
 * Modifiers must be placed after the parameter 
 * (e.g. /:foo?, /(test)?, /:foo(test)?, or {-:foo(test)}?).
 */

// "Optional" 修饰符
// Parameters can be suffixed with a question mark (?) 
// to make the parameter optional.
const p8 = pathToRegexp("/:foo/:bar?");
console.log(p8, p8.exec("/test"));
console.log(p8, p8.exec("/test/route"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// "Zero or more" 修饰符
// Parameters can be suffixed with an asterisk (*) 
// to denote a zero or more parameter matches.
const p9 = pathToRegexp("/:foo*");
console.log(p9, p9.exec("/"));
console.log(p9, p9.exec("/bar/baz"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// "Zero or more" 修饰符
// Parameters can be suffixed with an asterisk (*) 
// to denote a zero or more parameter matches.
const p9 = pathToRegexp("/:foo*");
console.log(p9, p9.exec("/"));
console.log(p9, p9.exec("/bar/baz"));
console.log(p9, p9.exec("/bar/baz/xyz"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

// "One or more" 修饰符
// Parameters can be suffixed with a plus sign (+) 
// to denote a one or more parameter matches.
const p10 = pathToRegexp("/:foo+");
console.log(p10, p10.exec("/"));
console.log(p10, p10.exec("/bar/baz"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");

/*
 * 匹配(match)
 * The match function will return a function for transforming paths into parameters:
 */
// Make sure you consistently `decode` segments.
const fn = match("/user/:id", { decode: decodeURIComponent });
console.log(fn("/user/123") );
console.log(fn("/invalid"));
console.log(fn("/user/caf%C3%A9"));
console.log("-----------------------");
代码语言:javascript
复制
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");
/*
 * Compile ("Reverse" Path-To-RegExp)
 * The compile function will return a function for transforming parameters into a valid path:
 */
// Make sure you encode your path segments consistently.
const toPath = compile("/user/:id", { encode: encodeURIComponent });

console.log(toPath({ id: 123 }));
console.log(toPath({ id: "café" }));
console.log(toPath({ id: "/" }));
console.log(toPath({ id: ":/" }));

6.4. history 是干什么的?

The history library lets you easily manage session history anywhere JavaScript runs. A history object abstracts away the differences in various environments and provides a minimal API that lets you manage the history stack, navigate, and persist state between sessions.

The history library is a lightweight layer over browsers' built-in History(window.history) and Location APIs. The goal is not to provide a full implementation of these APIs, but rather to make it easy for users to opt-in to different methods of navigation.

We provide 3 different methods for creating a history object, depending on the needs of your environment:

  • createBrowserHistory is for use in modern web browsers that support the HTML5 history API (see cross-browser compatibility)
  • createHashHistory is for use in situations where you want to store the location in the hash of the current URL to avoid sending it to the server when the page reloads
  • createMemoryHistory is used as a reference implementation and may also be used in non-DOM environments, like React Native or tests

history 使用示例:

代码语言:javascript
复制
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

// Get the current location.
const location = history.location;

// Listen for changes to the current location.
const unlisten = history.listen((location, action) => {
  // location is an object like window.location
  console.log(action, location.pathname, location.state);
});

// Use push, replace, and go to navigate around.
history.push('/home', { some: 'state' });

// To stop listening, call the function returned from listen().
unlisten();

6.5. react-router-dom 与 react-router 是什么联系?

react-router-dom 只是 react-router 的一个套壳

react-router 中持有核心逻辑

我们来看一下 react-router-dom 是如何对外导出模块的:

代码语言:javascript
复制
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/index.js

再来看一下 react-router-dom 的 BrowserRouter、HashRouter 干了什么:

所以,react-router-dom 只是一层壳

内核都在 react-router 中

6.6. 内核 react-router.Router 概览

  1. Router 持有一个 history 对象(BrowserRouter 持有的是 BrowserHistory,HashRouter 持有的是 HashHistory)
  2. Router 还持有体现当前路由状态的 location 对象(这个对象由 history 提供,每次路由变化,这个对象都将是一个新的)。
  3. Router 通过 history 对象提供的监听,将 location 的变化实时反馈到自身 state 上,借此进行后续的 UI 更新。
  4. Router 通过 <RouterContext>、<HistoryContext> 向下级元素提供环境数据。

6.7. 内核 react-router.Route 分析

<Route> 的核心作用是,根据当前路由位置(location,这个默认从 <RouterContext> 上下文中获取,也可以自行制定)以及路由路径(path),判定是否匹配。根据匹配情况,决定 component、render、children 的渲染策略。

  • 接口概览
  • 总体逻辑
  • 路径匹配逻辑(matchPath.js)

6.8. 内核 react-router.Switch 分析

<Switch> 组件的直接子元素可以是多个 <Route> 组件,<Switch> 的用途是,找到子元素中第一个能够匹配的 <Route>,并通知它渲染,忽略其他元素。

  • 接口概览
  • 逻辑分析

6.9. 内核 react-router.Prompt 分析

<Prompt> 用于实现路由跳转拦截,比如当用户修改了数据但还没由提交,如果此时用户切换路由,就可以给出 comfirm 提示用户,是否确认要进行路由切换。

  • 接口概览
  • 逻辑分析
    • <Prompt> 本质上是利用 history 对象的 block 接口(即 history 的过渡控制功能)实现的路由跳转拦截。(当然 ReactRouter's history 的路由拦截特性比较简单,也就是使用 window.prompt 提示一下。VueRouter's history 的路由拦截就强大很多)

6.10. 内核 react-router.Redirct 分析

<Redirect> 用于实现路由重定向,通常跟路由鉴权结合起来使用。比如当用户访问 "/" 路由时,如果用户已经登录过了,那么重定向到 /home;如果用户还没有登录,那么就重定向到 /login。

  • 接口概览
  • 逻辑分析
    • <Redirect> 本质上还是利用 history 对象提供的 push、replace 功能,配合 React 的生命周期,实现路由跳转。

6.11. 周边 react-router.Lifecycle.js 分析

Lifecycle.js 在 react-router 内部被多次使用,例如:

  • react-router.Redirect 中
  • react-router.Prompt 中
  • ...

Lifecycle.js 的本质是复用组件的生命周期逻辑。实现了类似 hooks 中的 useEffect 功能。

6.12. 周边 react-router.createNameContext.js 分析

react-router 核心由 2 个 Context 组成:

  • RouterContext:持有 history、location、match、staticContext 对象;
  • HistoryContext:持有 history 对象(HashHistory 或 BrowserHistory);

react-router 出于兼容低版本 React 的考虑,使用了一版 Context API 的 polyfill 实现(mini-create-react-context)。

6.13. 周边 react-router.withRouter.js 分析

react-router 的 withRouter 是用于给组件注入路由状态的高阶组件。如果你已经开始使用 react hooks,那直接使用 react-router 的hooks api 即可,不需要再使用 withRouter 了。

6.14. 周边 react-router.hooks.js 分析

react-router 针对 react hooks,也提供了一套声明式 API:

  • useHistory
  • useLocation
  • useParams
  • useRouteMatch

实现也比较简单,因为这些数据,都存储在 RouterContext 或 HistoryContext 中,直接提取即可。

参考:

Angular、React、Vue 路由解决方案: https://angular.io/guide/router https://reacttraining.com/react-router/ https://github.com/ReactTraining/react-router/ https://github.com/ReactTraining/history https://router.vuejs.org/zh/ 路由原理: https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event http://blog.httpwatch.com/2011/03/01/6-things-you-should-know-about-fragment-urls/ https://developer.mozilla.org/zh-CN/docs/Web/API/History_API 路由路径匹配: https://github.com/pillarjs/path-to-regexp 路由集成方案: https://umijs.org/zh-CN/docs/routing https://pro.ant.design/docs/router-and-nav-cn https://nextjs.org/docs/routing/introduction https://panjiachen.github.io/vue-element-admin-site/zh/ https://lison16.github.io/iview-admin-doc/#/%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档