6. React Router 关键源码分析
6.1. 库结构
6.2. react-router 的关键依赖项?
6.3. path-to-regexp 是干什么的?
react-router 的路径识别、匹配就是靠的 path-to-regexp
示例:(特别注意,下面是基于"path-to-regexp": "^6.2.0" 搞的测试)
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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("-----------------------");
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:
history 使用示例:
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 是如何对外导出模块的:
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 概览
6.7. 内核 react-router.Route 分析
<Route> 的核心作用是,根据当前路由位置(location,这个默认从 <RouterContext> 上下文中获取,也可以自行制定)以及路由路径(path),判定是否匹配。根据匹配情况,决定 component、render、children 的渲染策略。
6.8. 内核 react-router.Switch 分析
<Switch> 组件的直接子元素可以是多个 <Route> 组件,<Switch> 的用途是,找到子元素中第一个能够匹配的 <Route>,并通知它渲染,忽略其他元素。
6.9. 内核 react-router.Prompt 分析
<Prompt> 用于实现路由跳转拦截,比如当用户修改了数据但还没由提交,如果此时用户切换路由,就可以给出 comfirm 提示用户,是否确认要进行路由切换。
6.10. 内核 react-router.Redirct 分析
<Redirect> 用于实现路由重定向,通常跟路由鉴权结合起来使用。比如当用户访问 "/" 路由时,如果用户已经登录过了,那么重定向到 /home;如果用户还没有登录,那么就重定向到 /login。
6.11. 周边 react-router.Lifecycle.js 分析
Lifecycle.js 在 react-router 内部被多次使用,例如:
Lifecycle.js 的本质是复用组件的生命周期逻辑。实现了类似 hooks 中的 useEffect 功能。
6.12. 周边 react-router.createNameContext.js 分析
react-router 核心由 2 个 Context 组成:
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:
实现也比较简单,因为这些数据,都存储在 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