浏览源码小 tips:从整体到细节,刚开始不要太拘泥于一行代码的实现,先从整体去了解,了解好各自的关系,再去读源码。
首先,咱们先不纠结于源码细节。先用最简略的话来概括一下 React-router 到底做了什么?
实质上,React-Router 就是在页面 URL 发生变化的时候,通过咱们写的 path 去匹配,而后渲染对应的组件。
那么,从这句话,咱们想一下如何分步骤实现:
- 如何监听 url 的变动?
- 如何匹配 path,按什么规定?
- 渲染对应的组件
理解好须要实现的关键步骤,咱们来将仓库源码下载下来。
接下来咱们看一下 GitHub
, 它应用 lerna
治理同时治理多个包. 也就是Multirepo
概念。
react-router 应用 lerna 来同时治理多个包. (lerna 的益处特地多,对于依赖关系大,同类型的包举荐应用 lerna 来对立治理。)
外围库是 react-router. react-router-dom 是在浏览器中应用的,react-router-native 是在 rn 中应用的。
如果不了解,间接看一下源码就懂了。其实 react-router-dom 只是多了上面四个组件 BrowserRouter、Link、NavLink、HashRouter,其余其实都是间接援用 react-router 的。
理解完多包的组织关系之后,咱们回到后面如何实现 react-router
的 3 个关键步骤,如下:
- 如何监听
url
的变动? - 如何匹配
path
? - 渲染对应的组件
咱们不本人来实现,间接看源码,站在伟人的肩膀上来学习😄。接下来咱们来看一下 react-router-dom
官网文档 的根本应用。
export default function App() {
return (
<BrowserRouter>
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/topics">Topics</Link>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
从代码中,咱们能够察看到上面几点:
- 最外层包裹了
<BrowserRouter>
,它有什么意义? - 在
<Route />
匹配的外层,包裹了<Switch>
,作用是如果匹配了一个,则不会再持续渲染另外一个。如何实现? - Route 中有
path
匹配门路,包裹的则是渲染的组件。
整体设计
咱们用一张图来了解一下整个 react-router 是怎么实现的:
接下来咱们看看每一个步骤是怎么实现的。
一、监听 URL 的变动
失常状况下,当 URL 发生变化时,浏览器会像服务端发送申请,但应用以下 2 种方法不会向服务端发送申请:
- 基于 hash
- 基于 history
react-router 应用了 history 这个外围库。
1. 抉择形式: history 或 hash
HashRouter 先是从 history 中援用 createBrowserHistory,而后将 history 和 children 传入到 Router。BrowseHistory 同理。
BrowseHistory 必须依赖服务器让 url 都映射到 index.html,否则会 404。
2. 监听 URL 的变动,拿到对应的 history,location,match 等通过 Provider 注入到子组件中。
二、Route 中匹配渲染组件
这代码能够分两局部了解:
- 是否匹配
- 渲染组件
1. 是否匹配
computedMatch 是应用 Switch 包裹的子组件才有的值,Switch 的作用是从上到下开始渲染,只有匹配到一个,其余的就不匹配。所以这里会先判断 computedMatch。
匹配解析 path,这里应用了第三方库 path-to-regexp
// Make sure you consistently `decode` segments.
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} />
源码局部如下:
吐槽一下,作者怎么就不能好好用 if else 来写,非要写这么多变态的?:,请不要学习,除非你的我的项目只有你一个前端😂。
一下子看不懂也没关系,咱们来看上面的流程图。
从下面的代码咱们能够看出:
- Router 渲染的优先级:children > component > render,三种形式互斥,只能应用一种。
- 不匹配的状况下,只有 children 是函数,也会渲染
- component 是应用 createComponent 来创立的,这会导致不再更新现有组件,而是间接卸载再去挂载一个新的组件。如果是应用匿名函数来传入 component,每次 render 的时候,这个 props 都不同,会导致从新渲染挂载组件,导致性能特地差。因而,当应用匿名函数的渲染时,请应用 render 或 children。
// 不要这么应用
<Route path="/user/:username" component={() => <User/>} />
论断
对于依赖关系大,同类型的包应用 lerna
来对立治理。尽量形象出共用不可变的中央,比方 react-router
中的办法。
React-router
应用了 Compound components
(复合组件模式),在这种模式中,组件将被一起应用,它们能够不便的共享一种隐式的状态,比方 Switch
, 能够在这里通过 React.children
来管制包裹组件的渲染优先级,而无须使用者去管制。再比方咱们常常应用的 <select />
和 <option>
, 能够通过 React.children
和 React.cloneElement 来劫持批改子组件,让组件使用者通过更少的 api 来触发更弱小的性能。
公众号:前端加加
更多精彩文章,尽在前端加加,欢送关注探讨。
我组建了一个气氛特地好的阿里巴巴内推社群,如果你对退出阿里巴巴感兴趣的话(后续有打算也能够),咱们能够一起进行面试相干的答疑、聊聊面试的故事、并且在你筹备好的时候随时帮你内推。下方加 peen 好友回复「面试」即可。