「原创」图解 React-router 带你深入理解路由本质

阅读源码小 tips:从整体到细节,刚开始不要太拘泥于一行代码的实现,先从整体去理解,理解好各自的关系,再去读源码。

首先,我们先不纠结于源码细节。先用最简单的话来概括一下 React-router 到底做了什么?

本质上, React-Router 就是在页面 URL 发生变化的时候,通过我们写的 path 去匹配,然后渲染对应的组件。

那么,从这句话,我们想一下如何分步骤实现:

  1. 如何监听 url 的变化 ?
  2. 如何匹配 path,按什么规则 ?
  3. 渲染对应的组件

了解好需要实现的关键步骤,我们来将仓库源码下载下来。

接下来我们看一下 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个关键步骤,如下:

  1. 如何监听 url 的变化 ?
  2. 如何匹配 path ?
  3. 渲染对应的组件

我们不自己来实现,直接看源码,站在巨人的肩膀上来学习😄。接下来我们来看一下 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>
  );
}

从代码中,我们可以观察到下面几点:

  1. 最外层包裹了<BrowserRouter> ,它有什么意义?
  2. <Route />匹配的外层,包裹了<Switch>,作用是如果匹配了一个,则不会再继续渲染另外一个。如何实现?
  3. 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. 是否匹配
  2. 渲染组件
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 来写,非要写这么多变态的 ?:,请不要学习,除非你的项目只有你一个前端😂。

一下子看不懂也没关系,我们来看下面的流程图。

从上面的代码我们可以看出:

  1. Router 渲染的优先级:children >  component > render,三种方式互斥,只能使用一种。
  2. 不匹配的情况下,只要 children 是函数,也会渲染
  3. component 是使用 createComponent 来创建的, 这会导致不再更新现有组件,而是直接卸载再去挂载一个新的组件。如果是使用匿名函数来传入 component ,每次 render 的时候,这个 props 都不同,会导致重新渲染挂载组件,导致性能特别差。因此,当使用匿名函数的渲染时,请使用 render 或 children 。
// 不要这么使用<Route path='/user/:username' component={() => <User/> } />

结论

  1. 对于依赖关系大,同类型的包使用 lerna 来统一管理。尽量抽象出共用不可变的地方,比如 react-router 中的方法。

  2. React-router 使用了Compound components(复合组件模式),在这种模式中,组件将被一起使用,它们可以方便的共享一种隐式的状态,比如 Switch , 可以在这里通过 React.children 来控制包裹组件的渲染优先级,而无须使用者去控制。再比如我们经常使用的 <select /><option>, 可以通过 React.children 和 React.cloneElement 来劫持修改子组件,让组件使用者通过更少的 api 来触发更强大的功能。

(0)

相关推荐

  • 「原创」海丰阿东:出门带三宝,现在成二宝

    「原创」海丰阿东:出门带三宝,现在成二宝 不论是男人还是女人出门的话 有三样东西是不能忘记,要带在身上的.这三样东西就是 钥匙,手机和钱.生活就是这样,只要你留心去观察,处处都有风景,到处都可以学到东 ...

  • 关羽麦城突围时为什么不带周仓?「原创」

    周仓,在<三国演义>里是关羽的贴身侍卫,职业"捧刀人",也是一位"福将".当年关羽"单刀赴会",谁也不带,唯独带着周仓,谈判时, ...

  • 「原创」海丰阿东:会吃孩子的妈妈

    「原创」海丰阿东:会吃孩子的妈妈 我们听到过许多关于孩子和父母的故事.那孩子可是母亲的心头肉,哪有把孩子吃掉的道理.母亲不会这样去做,可孩子是不会这样认为的.这不,就有这样一个小孩子这样询问他的妈妈. ...

  • 「原创」海丰阿东:好习惯慢慢的改变着社会,这就是文明

    「原创」海丰阿东:好习惯慢慢的改变着社会,这就是文明 随着社会的进步和经济的发展,我们发现整个社会人们的精神面貌也在不断的发生着改变,我们变得越来越文明了. 前些年我们经常在媒体上看到那些出国旅游的人 ...

  • 「原创」海丰阿东:有样学样,要形象更要神象

    「原创」海丰阿东:有样学样,要形象更要神象 看别人好就要去好好的向别人学习,让自己也能够变得好起来,这是一个简单的道理.学习别人好的地方来充实自己,提高自己.学习时候不但要形象还要神像,才算是真正的学 ...

  • 「原创」海丰阿东:行骗的,不骂你骂谁?

    「原创」海丰阿东:行骗的,不骂你骂谁? 如果你对一个陌生人开口就骂的话,一定会被别人说的:你这样做是不是太没礼貌了,是不是做的过分了? 说的对,如果一个人这样做,大多数情况下都会被人认为是做的不对的. ...

  • 「原创」海丰阿东:人生的境界怎么样才算是最高的?

    「原创」海丰阿东:人生的境界怎么样才算是最高的? 慢慢人生路,路上风景不断变幻,当你活得越久,对人生的体验就越丰富.放在更大的环境里面去看你的人生,你自己.你不过是那沧海一粟,对于浩渺无际的宇宙来说, ...

  • 大唐覆亡所源起的那场军人哗变「原创」

    据<新唐书卷114·崔彦曾传>.<新唐书·懿宗纪>中介绍,在公元868年的7月,也就是咸通九年,唐朝徐州节度使孟球奉命招募了两千名士兵,其中分派八百人去桂林戍边,当时约定三年后 ...

  • 「原创」海丰阿东:妻妾间的报应,一鞭还一鞭

    「原创」海丰阿东:妻妾间的报应,一鞭还一鞭 说起报应这种事情,相信的是肯定认为有,不相信的也就当个故事听听,不必认真. 从前有个财主名字叫李太学的.在家乡找了两个女人.就是常人所说的一妻一妾.家里妻子 ...