本篇文章参考以下博文
《图解React-router源码》–魔术师卡颂
前言
最近在与前辈的交流过程中,大佬抛出来一个问题, react 路由是如何实现的?平时在学习过程中,更多的是使用,还没有研究过这东西的工作原理,今天我们就来一起挖一挖,这东西是怎么做出来的。
路由原理
首先我们需要先有一个概念,那就是路由是怎么实现的?原理呢其实就是页面的 url 发生变化,然后去匹配路径,渲染对应组件。
接下来如何实现?
实现步骤
总的实现步骤可以分为3步:
- 如何监听 url 变化?
- 如何匹配 path 路径,匹配规则是什么?
- 如何渲染对应组件。
核心包有两个 react-router ,reat-router-dom 。
其中 react-router-dom 是在浏览器中使用的包,包含四个组件, BrowserRouter、Link、NavLink、HashRouter 。其他的都是引用 react-router 。
下面举一个常用例子来看。
export default function App() {
return (
<BrowserRouter>
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/more">More</Link>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
观察上面的代码,我们可以总结以下几点:
- 组件是被包裹在 <BrowserRouter> 标签里,这么做的意义是什么?
- <Switch> 标签作用是只匹配一个 path 路径,如何实现?
- <Route> 的属性中有 path 路径,内部是需要展示的组件。
我们先通过一张图来理解以下 react-router 的流程。
一、监听 url 变化
正常情况下,当 url 发生变化时,浏览器会向服务器发送请求,但是使用以下两种方法不会向服务器发送请求。
- 基于 hash
- 基于 history
1. 选择方式: history 或 hash
HashRouter 先是从 history 中引用 createBrowserHistory ,然后将 history 和 children 传入到 Router 。 BrowseHistory 同理。
注意: BrowseHistory 必须依赖服务器让 url 都映射到 index.html ,否则会 404 。
2. 监听 URL 的变化
拿到对应的 history,location,match 等通过 Provider 注入到子组件中
二、Route 中匹配渲染组件
(由于 return 里面不能写 if else ,三元运算符叠加起来使用,阅读是有点麻烦)
这代码可以分两部分理解:
- 是否匹配
- 渲染组件
1. 是否匹配
computedMatch 是使用 Switch 包裹的子组件才有的值, Switch 的作用是从上到下开始渲染,只要匹配到一个,其他的就不匹配。所以这里会先判断 computedMatch
匹配解析 path ,这里使用了第三方库 path-to-regexp
解析规则如下:
const fn = match("/user/:id", { decode: decodeURIComponent });
fn("/user/123"); //=> { path: '/user/123', index: 0, params: { id: '123' } }
fn("/invalid"); //=> false
fn("/user/caf%C3%A9"); //=> { path: '/user/caf%C3%A9', index: 0, params: { id: 'café' } }
2. 组件渲染方式
文档中支持三种渲染方式,分别是:
// children 方式
<Route exact path="/">
<HomePage />
</Route>
// func 方式
<Route
path="/blog/:slug"
render={({ match }) => {
// Do whatever you want with the match...
return <div />;
}}
/>
// component 方式
<Route path="/user/:username" component={User} />
源码部分显示如下:
这部分有点乱,需要结合流程图理解。
从上面的代码我们可以看出:
- Router 渲染的优先级: children > component > render ,三种方式互斥,只能使用一种。
- 不匹配的情况下,只要 children 是函数,也会渲染
- component 是使用 createComponent 来创建的, 这会导致不再更新现有组件,而是直接卸载再去挂载一个新的组件。如果是使用匿名函数来传入 component ,每次 render 的时候,这个 props 都不同,会导致重新渲染挂载组件,导致性能特别差。因此,当使用匿名函数的渲染时,请使用 render 或 children 。
// 不要这么使用
<Route path="/user/:username" component={() => <User/> } />
路由的基本功能,粗略介绍了下,具体还有很多细节优化的地方大家可以自己查看源码,继续学习。