react-router 源码浅析

用 react-router 也用了比较久了,对他的内部工作方式却只是了解皮毛,而且大部分还是通过别人的博客。最近两周打算自己探究一下他的实现。 注意!因为我只使用过 v3 版本的 react-router,因为对他的使用方式比较熟悉,所以这次解析也是基于这个版本。

文章目录:

  • react-router 工作模式简化流程
  • 内部具体实现
  • Link 组件的实现方式 以及 *History.push 的实现方式
  • 为什么要这样实现?有什么别的方式?做个比较?

react-router 工作模式简化流程

聊到这个话题就离不开前端路由。关于前端路由的一些演变过程和现有的方式可以看这篇文章。前端路由的重点就是不刷新页面,现有的解决方案有 hashChange 和 popState 两种。 React 提供API也是围绕这两种方式。 共同点都是发布订阅的模式,让浏览器事件触发的时候自己添加的 listener 被调用。Router 组件包裹着 Route 组件,Route 组件负责描述每条路由的展示组件和匹配的路径。这样 Router 组件实际上会格式化出一个映射的路由表
然而这是在页面路由更新的时候,最开始进入页面的时候怎么办呢?其实刚进入页面的时候也会进行一次匹配,详细分析见下一部分。

内部具体实现

首先解答上面的遗留问题,刚进入页面的状态如何带入?这个问题我们可以和 "Router 组件是在什么时候添加的事件监听"放在一起解答。

  componentWillMount() {//来源:modules/Router.jsthis.transitionManager = this.createTransitionManager()this.router = this.createRouterObject(this.state)this._unlisten = this.transitionManager.listen((error, state) => {if (error) {this.handleError(error)} else {// Keep the identity of this.router because of a caveat in ContextUtils:// they only work if the object identity is preserved.assignRouterState(this.router, state)this.setState(state, this.props.onUpdate)}})},

Router 组件在 willMount 生命周期添加了 listener,而添加 listener 本身就会触发一次匹配路由展示的过程。匹配的过程有 match 方法,用于各种嵌套路由的匹配。
但是注意,如果使用的是 browserHistory,这种路由方式一般是/a/b 这种方式,可能需要后端同学的配合。

封装 history ——transitionManager

在上面的代码中,我们会注意到添加监听器的 listen方法来自于 transitionManager 这个生成之后被赋值到 this.router 实例的属性。实际上 react-router 的事件监听过程是用 transitionManager 套了 history 这个库,抹平各种前端路由方式的调用差异。history库本身暴露了一些API 比如监听、取消监听、跳转等一系列方法。有基于咱们刚才提到的 hash 和 state 两种方式。我们传给 Router 组件 history 属性的值其实就是他的实例。(拿hashHistory 举栗,下面的文件是 reate-router export 的 hashHistory 的来源,也就是我们用的 hashHistory 的来源)。

//来源:modules/hashHistory.js
import createHashHistory from 'history/lib/createHashHistory'
import createRouterHistory from './createRouterHistory'
export default createRouterHistory(createHashHistory)

而 transitionManager 做的事情是针对当前的 router 实例和开发者指定的 history 对象,对 history 库给的 API 做一次二次封装,加上修改路由状态等等操作。然后开发者拿着 transitionManager 封装之后暴露出的 listen 等方法操作路由。

  createTransitionManager() {//来源:modules/Router.jsconst { matchContext } = this.propsif (matchContext) {return matchContext.transitionManager}const { history } = this.propsconst { routes, children } = this.propsinvariant(history.getCurrentLocation,'You have provided a history object created with history v4.x or v2.x '  'and earlier. This version of React Router is only compatible with v3 '  'history objects. Please change to history v3.x.')return createTransitionManager(//注意这个createTransitionManager才是history,createRoutes(routes || children))},
渲染部分

渲染过程不是放在 Route 组件中负责渲染,而是把状态都放在 Router 中保存,详细可见第一部分的代码添加 listener 的部分。

     assignRouterState(this.router, state)this.setState(state, this.props.onUpdate)

而 Router 组件的 render 是这样写的:

    const { location, routes, params, components } = this.stateconst { createElement, render, ...props } = this.propsreturn render({...props,router: this.router,location,routes,params,components,createElement})

而 props 的值是当前 Router 组件的状态,他现在要展示的组件,对应的地址,当前跳转携带的参数 params 等等。下面是调用 render 的部分。

  return <RouterContext {...props} />

RouterContext 包装组件的主要作用就是把 props参数中存有当前路由状态的对象router存到全局。类似于 Redux 的 Provider 组件。

Link 组件的实现方式

这里小伙伴们可以猜测一下,Link是怎么做的呢?
我们知道 Link最后渲染完是个 a 标签,我们通常会给 Link 组件几个参数,最常用的是跳转的路由地址和携带的参数。通过上面的讲解不难猜出,Link 在点击的时候应该是调用了一个跳转的操作(八成也是 history 库里给的),然后禁止掉默认跳转就行了。
事实也是如此,history 暴露了一个 push方法,来 push 进浏览器的历史访问栈中。 这里再提一句另外一种用法:*history.push() 的方式。这种其实就相当于直接点击了 a 标签一样的道理,只不过用 js 的方式实现了。

为什么要这样实现?有什么别的方式?做个比较?

我们可不可以尝试把展示交给 route 组件管理?router 只控制激活当前的 route?但是这样就不能支持通过 props 传给 router 路由配置的方式使用了,这是其一;
其二,这样其实 route 其实负责了组件的渲染工作,而不是把所有的状态和路由信息全部放在 router 中管理了,不方便集中维护和扩展。 不知道小伙伴们还有别的看法吗?

(本来想写个浅析的……噼里啪啦写了一大堆……还捎带点语无伦次……)
(但是虽然说了一堆……不过确实挺浅的……各位有兴趣可以尝试自己扒一下源码。建议 react-router 和 history 库一起 debug,更有助于我们融会贯通)
( 各位见笑了)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前5个有用的隐藏Eclipse功能

Eclipse是野兽。 仅凭其力量才能超越其神秘感的设备。 有人将其称为连续体跨功能器 。 其他人则称它为透湿器 。 是的&#xff0c;它是如此之大&#xff0c;需要花费数年才能掌握。 然后&#xff0c;您的经理出现并告诉您&#xff1a;我们正在使用NetBeans。 开玩笑。 除了Ada…

linux如何解除密码,如何在Linux下解除PDF文件的密码?

【51CTO.com快译】今天&#xff0c;我碰巧与一位朋友共享一个受密码保护的PDF文件。我知道该PDF文件的密码&#xff0c;但不想透露。相反&#xff0c;我只想解除密码&#xff0c;将文件发送给朋友。于是我开始在网上找一些简单的方法&#xff0c;好解除PDF文件的密码保护。上网…

C#中结构体定义并转换字节数组

ref: https://www.cnblogs.com/dafanjoy/p/7818126.html C#中结构体定义并转换字节数组 最近的项目在做socket通信报文解析的时候&#xff0c;用到了结构体与字节数组的转换&#xff1b;由于客户端采用C开发&#xff0c;服务端采用C#开发&#xff0c;所以双方必须保证各自定义结…

解析robots.txt

案例&#xff1a; http://www.taobao.com/robots.txt 学习&#xff1a; User-agent: * 这里的*代表的所有的搜索引擎种类&#xff0c;*是一个通配符Disallow: /admin/ 这里定义是禁止爬寻admin目录下面的目录Disallow: /require/ 这里定义是禁止爬寻require目录下面的目录Disal…

2018移动端页面适配-自适应最新方案直接写px--------通过gulp工作流搭建一体化的移动端开发环境

1.开始 在flexible的GitHub上面写着 由于viewport单位得到众多浏览器的兼容&#xff0c;lib-flexible这个过渡方案已经可以放弃使用&#xff0c;不管是现在的版本还是以前的版本&#xff0c;都存有一定的问题。建议大家开始使用viewport来替代此方案。vw的兼容方案可以参阅《如…

jclouds的命令行界面

序幕 我使用和为jclouds贡献了一年多的时间。 到目前为止&#xff0c;我已经在很多领域广泛使用了它&#xff0c;尤其是在Fuse生态系统中 。 它的强大之处在于它缺少一件事&#xff0c;该工具可用于管理jclouds也提供访问权限的任何云提供商。 类似于EC2命令之类的工具&#xf…

中兴linux下载软件,国产操作系统中兴新支点使用WPS For Linux办公软件的体验报告...

以下将给你带来在国产操作系统中兴新支点操作系统下使用WPS For Linux办公软件的体验报告&#xff0c;WPS For Linux提供Deb、Rpm、Tar.xz、Snap软件包&#xff0c;你可以选择Tar.xz源码包编译安装&#xff0c;或在系统自带的软件中心下安装&#xff0c;也可以参考采用snap方式…

Java 教程(开发环境配置+基础语法)

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境。 window系统安装java 下载JDK 首先我们需要下载java开发工具包JDK&#xff0c;下载地址&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/index.html&#xff0c;点击如下下载按钮&am…

数据采集工具Telegraf:简介及安装

接着上一篇博客&#xff1a;InfluxDB简介及安装&#xff0c;这篇博客介绍下Linux环境下Telegraf安装以及其功能特点。。。 官网地址&#xff1a;influxdata 官方文档&#xff1a;telegraf文档 环境&#xff1a;CentOS7.4 64位 Telegraf版本&#xff1a;0.11.1-1 一、Telegraf介…

初探小程序插件

插播公司招聘信息&#xff1a; https://cnodejs.org/topic/5a915706653c43b914684f90 小程序插件可以干嘛&#xff1f; 周二晚上&#xff08;3.13&#xff09;的一个小程序新功能发布了-【小程序插件】&#xff0c;一开始以为是小程序发布了类似npm的组件管理工具&#xff0c;…

流畅和稳定的API的Lambda

几周前&#xff0c;我写了关于Java 8 lambda的介绍 。 在本简介中&#xff0c;我解释了什么是lambda以及如何将它们与Java 8中也引入的新Stream API结合使用。 Stream API为集合提供了更实用的接口。 此接口在很大程度上取决于lambda。 但是&#xff0c;lambda不仅具有改进的收…

linux 内存使用原理,linux中内存使用原理

首先介绍一下linux中内存是如何使用的。当有应用需要读写磁盘数据时&#xff0c;由系统把相关数据从磁盘读取到内存&#xff0c;如果物理内存不够&#xff0c;则把内存中的部分数据导入到磁盘&#xff0c;从而把磁盘的部分空间当作虚拟内存来使用&#xff0c;也称为Swap。如果给…

Confluence 6 站点备份和恢复

Atlassian 推荐针对生产环境中安装使用的 Confluence 使用原始数据库工具备份策略。 在默认的情况下&#xff0c;Confluence 每天都会备份所有数据和附件到 XML 文件备份中。这些文件被称为 XML 站点备份&#xff0c;同时这些文件存储在 Confluence home 目录中的 backups 目录…

休眠事实:等于和HashCode

每个Java对象都继承了equals和hashCode方法&#xff0c;但它们仅对Value对象有用&#xff0c;对面向无状态行为的对象毫无用处。 尽管使用“ ”运算符比较引用很简单&#xff0c;但是对于对象相等而言&#xff0c;事情要复杂一些。 由于您负责告诉平等性对特定对象类型的含义…

从mysql向HBase+Phoenix迁移数据的心得总结

* 转载请注明出处 - yosql473 - 格物致知&#xff0c;经世致用 mysql -> HBase Phoenix 1.总体方案有哪些&#xff1f; 1&#xff09;通过Sqoop直接从服务器(JDBC方式)抽取数据到HBase中 因为数据量非常大&#xff0c;因此优先考虑用Sqoop和MR抽取。 使用Sqoop抽取数据有一…

玩转异步 JS :async/await 简明教程(附视频下载)

课程介绍 在软件开发领域&#xff0c;简洁的代码 > 容易阅读的代码 > 容易维护的代码&#xff0c;而 ES2017 中的 async/await 特性能让我们编写出相比回调地狱和 Promise 链式调用更直观、更容易理解的代码&#xff0c;await 关键字接收一个 Promise&#xff0c;等待代码…

linux 无法找到函数定义,找到定义Linux函数的位置

使用手册页对于基本的C函数&#xff0c;该手册页应该工作。man 2 readman 3 printf第2节为系统调用(直接到内核)&#xff0c;而第3是用于标准C库调用。您通常可以省略该部分&#xff0c;并且人将自己弄清楚您需要什么。请注意&#xff0c;您可能需要采取额外步骤在系统上获取与…

序列自动机—— [FJOI2016]所有公共子序列问题

序列自动机&#xff1a; 是一个处理子序列的自动机。就这样。 建造&#xff1a;&#xff08;By猫老师&#xff1a;immoralCO猫&#xff09; s[] next[][26] memset(next[n], -1, 26<<2); for(int i n; i; --i) {memcpy(next[i - 1], next[i], 26 << 2);next[i - 1…

1000种对Java的响应没有死

当一篇评论发表1000条评论时&#xff0c;值得考虑一下。 我上周的社论“ 如果Java即将死&#xff0c;它肯定看起来非常健康 ”在各个开发人员社区中都感到不安 。 在Reddit&#xff0c;Hacker News和Slashdot之间&#xff0c;它收到了1000多个评论。 奇怪的是&#xff0c;很少…

java导包

下载响应的zip文件&#xff0c;就可以导入了&#xff0c;导入src目录也是可以的。 转载于:https://www.cnblogs.com/liaoxiaolao/p/9902062.html