大家好,我是若川。欢迎加我微信 ruochuan12今天分享一篇react-router源码文章,深入理解路由的本质。微信预计阅读只需8分钟。点击下方卡片关注我,或者查看源码系列文章。


阅读源码小 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"); //=> false
fn("/user/caf%C3%A9"); //=> { path: '/user/caf%C3%A9', index: 0, params: { id: 'café' } }
2. 组件渲染方式

从文档来看,它支持三种方式的渲染,如下:

// children 方式
<Route exact path="/"><HomePage />
</Route>// func 方式
<Routepath="/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 来触发更强大的功能。


最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你进群。


················· 若川出品 ·················

今日话题

写作其实是利他共赢的一种方式。长期输出有价值的内容,会给自己带来很多好处。所以交流群里常有人问如何高效学习前端,我有时会建议可以写博客输出。

一个愿景是帮助5年内前端人走向前列的公众号

可加我个人微信 ruochuan12,长期交流学习

推荐阅读

我在阿里招前端,我该怎么帮你?(现在还能加我进模拟面试群)

如何拿下阿里巴巴 P6 的前端 Offer

图解 React-router 源码相关推荐

  1. 精读《react-snippets - Router 源码》

    造轮子就是应用核心原理 + 周边功能的堆砌,所以学习成熟库的源码往往会受到非核心代码干扰,Router 这个 repo 用不到 100 行源码实现了 React Router 核心机制,很适合用来学习 ...

  2. 中秋福利 | 漂亮的React后台源码真情大放送

    中秋快乐 每逢佳节倍思亲,一年一度的中秋,你和谁一起度过?如果你和小编一样,漂泊在外,别忘记给远在家乡的父母打个电话,祝福他们中秋快乐,告诉他们自己还好,勿让他们挂念.在此小编,祝各位粉丝们" ...

  3. [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的?

    [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的? 0遍 +1 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主目录 与歌 ...

  4. React Native 源码分析(三)——Native View创建流程

    1.React Native 源码分析(一)-- 启动流程 2.React Native 源码分析(二)-- 通信机制 3.React Native 源码分析(三)-- Native View创建流程 ...

  5. 前端战五渣学React——JSX React.createElement() React.ReactElement()源码

    最近<一拳超人>动画更新第二季了,感觉打斗场面没有第一季那么烧钱了,但是剧情还挺好看的,就找了漫画来看.琦玉老师真的厉害!!!打谁都一拳,就喜欢看老师一拳把那些上来就吹牛逼的反派打的稀烂, ...

  6. 精读《React PowerPlug 源码》

    1. 引言 React PowerPlug 是利用 render props 进行更好状态管理的工具库. React 项目中,一般一个文件就是一个类,状态最细粒度就是文件的粒度.然而文件粒度并非状态管 ...

  7. React Fiber源码逐个击破系列-scheduler

    Scheduler是React中相对独立的模块,用于实现类似浏览器requestIdleCallback的功能.也是React16实现time slicing最核心的一个模块. 在我阅读React源码 ...

  8. React Context源码是怎么实现的呢

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

  9. 设计模式之职责链模式(图解+例题+java源码)

    模式定义 职责链模式(Chain of Responsibility Pattern):避免请求发送者和接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连成一条链,并且沿着这条链传递请求,直到 ...

  10. react源码总览(翻译)

    用react也有段时间了, 是时候看看人家源码了. 看源码之前看到官方文档 有这么篇文章介绍其代码结构了, 为了看源码能顺利些, 遂决定将其翻译来看看, 小弟英语也是半瓢水, 好多单词得查词典, 不当 ...

最新文章

  1. 链表list(链式存储结构实现)_5 线性表的链式存储结构
  2. 信息安全从业者必须接受的六个事实
  3. 【Android工具】安卓手机淘宝双十二活动脚本,1212淘宝冲能量半自动程序
  4. Python常用模块之re模块
  5. js获取网页面的高度和宽度
  6. Datawhale组队-Pandas(下)分类数据(打卡)
  7. Android Native 内存泄漏系统化解决方案
  8. NASM汇编语言与计算机系统04-实模式-屏幕显示不定长度的字符串(cmp/je)
  9. sql初学者指南_初学者SQL示例:SQL SELECT语句的用法
  10. Nginx配置同一个域名同时支持http与https两种方式访问
  11. 神奇技术:科学家借助AI从受害人脑中还原犯罪者样貌
  12. slam的回顾(一)
  13. 来了!微信小程序五款最受欢迎的UI框架解读
  14. 蓝牙sbc怎么解决_你不知道蓝牙耳机术语,看懂了才明白哪些是优质蓝牙耳机
  15. ASP.Net Core数据加密
  16. MySQL数据库餐厅点菜系统_餐馆点餐管理系统-数据库课程设计-MySQL
  17. Linus 批评英特尔的 LAM 代码,拒绝将其合并到内核
  18. 2021年中国定制家具行业现状分析:“量身定制”需求逐年增加[图]
  19. 声纹识别概述(1)初识
  20. audio驱动之codec和codec_dai

热门文章

  1. 拉取ftp服务器上的文件_winscp和云服务器,2步实现winscp将文件上传到腾讯云Linux云服务器...
  2. 2019.08.08学习整理
  3. python try 异常处理 史上最全
  4. Bootstrap使用-1
  5. 48、多用块枚举,少用for循环
  6. js 类数组arguments详解
  7. 2017.10.13java上机出现中的问题
  8. [转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)...
  9. sublime代码片段
  10. py2.7+pyqt4开发端口检测工具