浏览源码小 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"); //=> falsefn("/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 好友回复「面试」即可。