从零手写react-router

蛮多同学可能会觉得react-router很复杂, 说用都还没用明白, 还从0实现一个react-router, 其实router并不复杂哈, 甚至说你看了这篇博客以后, 你都会觉得router的核心原理也就那么回事

至于react-router帮助我们实现了什么东西我就不过多阐述了, 这个直接移步官方文档, 我们下面直接聊实现

另外: react-router源码有依赖两个库path-to-regexphistory, 所以我这里也就直接引入这两个库了,虽然下面我都会讲到基本使用, 但是同学有时间的话还是可以阅读以下官方文档

还有一个需要注意的点是: 下面我书写的router原理都是使用hooks + 函数组件来书写的, 而官方是使用类组件书写的, 所以如果你对hooks还不是很明白的话, 得去补一下这方面的知识, 为什么要选择hooks, 因为现在绝大多数大厂在react上基本都在大力推荐使用hook, 所以我们得跟上时代不是, 而且我着重和大家聊的也是原理, 而不是跟官方一模一样的源码, 如果要1比1的复刻源码不带自己的理解的话, 那你去看官方的源码就行了, 何必看这篇博文了

在本栏博客中, 我们会聊聊以下内容:

  1. 封装自己的生成match对象方法
  2. history库的使用
  3. RouterBrowserRouter的实现
  4. Route组件的实现
  5. SwitchRedirect的实现
  6. withRouter的实现
  7. LinkNavLink实现
  8. 聚合api

封装自己的生成match对象方法

在封装之前, 我想跟大家先分享path-to-regexp这个库

为什么要先聊这个库哈, 主要原因是因为react-router中用到了这个库, 我看了一下其实我们也没必要自己再去实现一个这个库(为什么没必要呢,倒并不是因为react-router没有实现我们就不实现, 而是因为这个库实现的功能非常简单, 但是细节非常繁琐, 有非常多的因素需要去考虑到我觉得没必要), 这个库做的事情非常简单: 将一个字符串变成一个正则表达式

我们知道, react-router的大致原理就是根据路径的不同从而渲染不同的页面, 那么这个过程其实也就是路径A匹配页面B的过程, 所以我们之前会写这样的代码

<Route path="/news/:id" component={
   News} /> // 如果路径匹配上了/news/:id这样的路径, 则渲染News组件

那么react-router他是怎么去判断浏览器地址栏的路径和这个Route组件中的path属性匹配上的?

path填写的如果是/news/:id这样的路径, 那么/news/123 /news/321这种都能够被react-router匹配上

我们能够想到的方法是不是大概可以如下:

将所有的path属性全部转换为正则表达式(比如/news/:id转换为/^\/news(?:\/([^\/#\?]+?))[\/#\?]?$/i), 然后将地址栏的path值取出来跟该正则表达式进行匹配, 匹配上了就要渲染相应的路由, 匹配不上就渲染其他的逻辑

path-to-regexp就是做这个事情的, 他把我们给他的路径字符串转换为正则表达式, 供我们匹配

安装: yarn add path-to-regexp -S

// 我们可以来随便试试这个库
import {
    pathToRegexp } from "path-to-regexp";

const keys = [];

// pathToRegexp(path, keys?, options?)
// path: 就是我们要匹配的路径规则
// keys: 如果你传递了, 当他匹配上以后, 会把相对应的参数key传递到keys数组中
// options: 给path路径规则的一些附加规则, 比如sensitive大小写敏感之类的
const result = pathToRegexp("/news/:id", keys);

console.log("result", result);

console.log(result.exec("/news/123")); // 输出 ["/news/123", "123", index: 0, input: "/news/123", groups: undefined]
console.log(result.exec("/news/details/123")); // 输出null
console.log(keys); // 输出一个数组, 数组的有一个对象{modifier: " name: "id", pattern: "[^\/#\?]+?", prefix: "/", suffix: ""}

当然, 这个库还有很多玩法, 他也不是专门为react-router实现的, 只是刚好被react-router拿过来用了, 对这个库有兴趣的同学可以去看看他的文档

我们使用这个库, 主要是为了封装一个公共方法,为后续我们写router源码的时候提供一些基石, 因为我们知道, react-router一旦路径匹配上了, 是会向组件里注入history, location等属性的, 这些东西我们要提前准备好, 所以我们此刻的目标很简单

如果一个path值跟指定的path正则匹配上了, 那么我们要生成一个包含了location, history等属性的对象, 供后续使用, 说的更直白一点就是要得到react-router中那个的match对象

我们会发现这个功能其实是独立的, 这样拆分出来他可以用在任何地方, 只要匹配我就生成一个对象, 我也不管你拿这个对象去干嘛不关我屁事, 这也是软件开发中的一种较好的开发方式, 大家可以停下来在这里仔细思考一下这样的好处

所以接下来我要做的事情非常简单, 就是封装一个跟处理路径相关的方法, 为后续我们开发其他router功能的时候提供基层支持

我们在react工程中自己建立一个react-router目录, 在其中新建一个文件pathMatch.js

这也意味着我们将不再从npm上拉react-router, 而是直接在自己的工程里引用自己的react-router

pathMatch.js中每一步都写上了注释, 应该能够帮助你很好的理解

// src/react-router/pathMatch.js
import {
    pathToRegexp } from "path-to-regexp";


/** *  * @param {String} path 传递进来的path规则 * @param {String} url 需要校验path规则的url * @param {Object} options 一些配置: 如是否精确匹配, 是否大小写敏感等 *  * 这个函数要做的事情非常简单, 当我调用这个函数并且传递了相应 * 参数以后, 这个函数需要返回给我一个对象, 对象成员如下 * { *  params: { 路径匹配成功以后的参数值, 匹配不上就是null *    key: value  *  }, *  path: path规则 *  url: 跟path规则匹配的那一段url, 如果匹配不上就是null *  isExact: 是否精确匹配 * } *  */
function pathMatch(path = "", url = "", options = {
   }) {
   
  // 所以在这个函数内部, 我们要做的事情如下:
  // 1. 调用path-to-regex库且根据配置来帮助我们进行匹配参数值
  // 2. 将匹配结果返回出去

  // 首先, 如果你读了这个path-to-regex的文档的话, 你会发现一个问题
  // 我们在react-router中传递exact为精确匹配, 而在该库中则是使用end
  // 所以我们第一步先将用户传递的配置对象变成path-to-regex想要的配置对象
  const matchOptions = getOptions(options);
  const matchKeys = []; // 这个matchKeys其实就是我们用来装匹配成功后参数key的数组

  // 然后在path-to-regexp中得到相对应的正则表达式
  const pathRegexp = pathToRegexp(path, matchKeys, matchOptions);

  // 这里我们要使用对应的正则表达式来匹配用户传递的url
  const matchResult = pathRegexp.exec(url); 

  console.log("matchResult", matchResult);
  // 如果没有匹配上, 那直接返回null了
  if( !matchResult ) return null;

  // 如果匹配上了, 我们知道他返回的是一个类数组, 我们需要将matchKeys和类数组进行遍历
  // 生成最终的match对象里的params对象
  const paramsObj = paramsCreator(matchResult, matchKeys);

  return {
   
    params: paramsObj,
    path,
    url: matchResult[0], // matchResult作为类数组的第0项就是匹配路径规则的部分
    isExact: matchResult[0] === url
  }
}

/** *  * @param {Object} options 配置对象 * 这个方法主要就是将用户传递的配置对象, 转换为path-to-regex 需要的配置对象 */
function getOptions({
     sensitive = false, strict = false, exact = false }) {
   
  const defaultOptions = {
   
    sensitive: false,
    strict
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值