乐趣区

关于javascript:图解-Reactrouter-带你深入理解路由本质

浏览源码小 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 好友回复「面试」即可。

退出移动版