React笔记(六) - 用小白能看懂的方式手写React-Router

Hi, Everyone, 好久不见, 其实想了很久, 到底要不要出一篇react-router的源码级博客, 怕自己的理解不够深, 误导了大家, 但是本着书写学习笔记的习惯, 笔者还是写下来了自己对react路由的理解, 如果有问题之处还请大牛指教, 也希望这篇博客可以帮助到正在学习router原理的你

本博客不会过度的去分析react自身的源码, 因为这些我相信大家从git上可以很轻易的拿到, 笔者是通过从0书写一个自己的react-router来实现跟react同样功能的方式来分享整个路由的思想, 这样也更好的让初入源码学习的同学更好的适应

react-router团队本身在实现router的时候引用了两个比较小的库, 一个叫做path-to-regexp, 一个叫做history, 所以笔者这里也将会直接引用这两个库, 当然势必对帮助大家对这两个库进行一个全面的熟悉和了解, 如果想了解这两个库是怎么写的, 可以移步笔者的另外的博客进行了解

目录结构

    1. path-to-regexp的了解
    1. history的了解
    1. Router的实现
    1. Route的实现
    1. Switch的实现
    1. withRouter的实现
    1. LinkNavLink的实现

在前期的Router编写中, 或许没办法直接演示, 如果小伙伴有看不太明白的地方可以直接提问或者等到Route组件写完然后看笔者的例子再回头看Router可能就醍醐灌顶了, 坚持到最会你就会发现大名鼎鼎的react-router也不过如此


1. path-to-regexp的使用

在书写一个自己的router之前, 笔者必须做一些铺垫, 首当其冲的就是认识path-to-regexp这个库

该库用于将一个字符串正则(路径正则, path regexp), React Router中用到了这个库, 笔者这里不再手写

// 我们书写的Route组件中的path属性, 有时候会写成下面这种形式
<Route path='/news/:year/:month/:day' component={
   news}/>

// 其中的path属性看着像正则却不是正则, 而path-to-regexp这个库就是帮助我们将
// /news/:year/:month/:day 转化为正儿八经的正则表达式, 然后router才会拿去比对和校验
// 如果不进行转化成真正的正则表达式, js是不认识的

这哥们接受三个参数, 并在执行调用完毕以后返回一个正则表达式, 我们可以用返回的正则表达式

参数 功能
path 要匹配的校验规则
keys path-to-regexp会将第一个参数path规则中的每一项的关键字抽出来包装在key三种传递给你
options 其他配置项,如是否开启大小写敏感, 是否精确匹配等

表格参数功能没看懂没关系, 笔者一开始也不是很懂, 但是你只要看看返回的数据就会秒懂了

我们来看看他的基本使用

import pathToRegexp from 'path-to-regexp';

const path = '/news/:year/:month/:day';
const keys = []; // 这个数组现在是空的, 待会我作为第二个参数丢进去, 他会在函数执行完以后给我一个有东西的数组

const result = pathToRegexp(path, keys, {
   
    sensitive: true, // 是否对大小写敏感
    end: true, // 是否精确匹配
});

console.log('keys如下: ', keys); // 会给我们一个数组, 将path参数中的关键字都抽离出来
console.log('根据path和配置项生成的正则如下: ', result); // 输出的就是一套正则表达式

输出结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vOQhT4FZ-1594540331059)(./blogImages/path-to-exp.png)]

返回的正则表达式就好像是说把我们的path规则和第三个参数配置项通过分析得出一个正则表达式, 而第二个参数keys只是为了帮助我们更好的进行后续操作准备的, 我们的path/news/:year/:month/:day, 于是keys中就将year, month, day给我们封装进去了, 后续在使用中这些可能会对我们有帮助, 但是我们也不会用到它, 你可以理解keys仅仅是一个辅助参数

OK, path-to-exp的基本了解说到这里, 因为react-router本身也是直接调用的这个库, 所以我们也因为篇幅问题自己就不写了


2. history是了解和使用

该对象提供了一些方法, 用于控制或监听地址的变化
该对象不是window.history, 而是一个抽离的对象, 它封装了具体的实现

我们来看看他的基本使用吧

import {
    createBrowserHistory } from 'history';

const browserHistory = createBrowserHistory();

console.log('打印出的browserHistory如下', browserHistory);

输出结果如下, 这哥们就是提供了这些方法二次封装了浏览器的history对象, 提供了更加强大的功能, 这些功能我们随着用随着说, 这里就点到为止, 或许在router中你会随着使用更加的清晰

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-suZpFg74-1594540331061)(./blogImages/history.png)]


3. Router组件的实现

害, 终于进入正题了, 一顿操作猛如虎, 全从Router开始撸

想要实现Router, 我们得先知道Router做了哪些事

  1. 这哥们本身不做任何的展示, 仅提供路由模式的配置
  2. 该组件会提供一个上下文, 上下文会提供一些使用的属性和方法, 供其他相关组件使用
  3. 浏览器中Router本身分为以下两种形式
    • HashRouter: 使用HashRouter模式匹配路由
    • BrowserRouter: 使用BrowserRouter模式展示路由

来吧, 来看个实例帮你们整体回忆一下

// App.js
import React from 'react';
import {
    BrowserRouter as Router } from 'react-router-dom';

export default function App(props) {
   
    return (
         <Router></Router>
    )
}

我们来看一下React Devtools中展示给我们的react结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IyXGxIbv-1594540331062)(./blogImages/router.gif)]

如果图片不够清晰, 或者你看不懂也没关系, 笔者再帮你分析一波

  1. BrowserRouter当我们引入以后, 其实他内部是还用到了一个核心的Router组件
  2. Router组件得到一个属性history为一个对象, 就是我们用history库构造出来的对象一模一样
  3. Router组件有一个状态location, 该location其实就是history属性中的location, 只是提出来作为属性而已
  4. Router提供一个上下文, 里面携带一个value属性, 为一个对象, 对象中内容如下
    • history: 来自于Router组件中的history属性, 保存了当前浏览器历史记录栈的一些方法和信息
    • location: 来自于Router组件的location状态, 保存了当前路由的一些信息
    • match: 用来判定当前路由跟我们之后要书写的Route组件的上的path规则的校验, 它来自于我们自己书写, match对象携带以下几个属性
      • isExact: Boolean, 是否精确匹配
      • params:
  5. Routerchildren会被渲染进页面

这上面的这些基本使用方法我就不再过多的分析了, 别来看Router源码了还问我HashRouter是什么, 说我没写清楚, 那就太尴尬了

那咱一点一点来实现?

src目录下新建一个react-router文件夹(当然你自己想建在哪就建在哪), 创建一个Router.js

// Router.js
import React from 'react';

export default class Router extends React.PureComponent {
   
    render() {
   
        {
   /*根据我们上面的说法, 这里其实是返回了一个上下文出去*/}
    }
}

所以我们先将上下文搞定, react-router目录下, 创建一个RouterContext.js

// RouterContext.js
import React from 'react';

const RouterContext = React.createContext();

RouterContext.displayName = 'Router'; // 设置上下文在React Devtools工具中的名称, 这个是一个小细节, 因为我们会发现ReactRouter的上下文在调试工具中显示的是Router.Provider, 就是通过这样改名实现的

export default RouterContext;

回到Router.js

// Router.js
import React from 'react';
import {
    default as ctx } from './RouterContext.js'

export default class Router extends React.PureComponent {
   
    render() {
   
        {
   /*根据我们上面的说法, 这里其实是返回了一个上下文出去*/}
        return (
            <ctx.Provider>
            </ctx.Provider>
        )
    }
}

这个时候我们引入我们自己的Router.js进App, 并进浏览器看一下结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PnacSNll-1594540331063)(./blogImages/RouterProvider.png)]

结构很明显已经出来了, 只是之前说好的属性都没有, 那我们得给他啊

Router组件接收一个属性history, 我们先写着, 等待后续的父组件给他

// Router.js
...
export default class Router extends React.PureComponent {
   

    state = {
   
        // 我们知道location也是从history属性中拿过来的
        location: this.props.history.location, 
    }

    render() {
   
        // 我们之前有看到之后提供的上下文里, 有一个value值
        // value值里面有history, location, match三个属性
        // 要传递给上下文的value对象
        const contextValue = {
   
            history: this.props.history,
            location: this.state.location,
            match: ?
        }

        return (    
            {
   /*将contextValue传递给Provider*/}
            <ctx.Provider value={
   contextValue}>
                {
    this.props.children } 
            </ctx.Provider>
        )
    }
}
...

其实我们目前知道historylocation最终一定是从父组件来的, 那么match呢, 这哥们是需要我们自己来构造的, 希望你没有忘记笔者之前说的path-to-regexp, 如果忘了赶紧回去看看,来吧

react-router目录下新建一个pathMatch.js

// pathMatch.js
import pathToRegExp from 'path-to-regexp';

// 我们知道pathToRegExp就是帮助我们将我们想要设置的浏览器路径规则变成正则表达式, 以方便我们进行比较的

// 写一个方法pathMatch, 他也是最终我们要导出的方法
/**
 * 根据调用该方法的人传进来的参数, 用来匹配路径是否符合路径规则, 匹配成功返回一个match对象, 匹配失败返回undefined
 * @param {*} path  路径规则
 * @param {*} pathname 真实的路径
 * @param {*} options  其他配置项: sensitive => 是否大小写敏感, strict => 是否开启严格模式, exact => 是否精确匹配
 */
export default function pathMatch(path, pathname, options) {
   
    const keys = []; // 设置关键字数组, 就跟我们一开始测试pathToRegexp的含义一样
}

我们之前知道, 用户传递进来的的是sensitive, strict, exact三个属性, 前两个都没有问题, 但是最后一个我们知道path-to-regexp里精确匹配是为end, 所以我们必须将用户传递进来的操作转换一下

// pathMatch.js
...
export default function pathMatch(path, pathname, options) {
   
    ...
}

/**
 * 将传入的react-router的配置转化为path-to-regexp的配置
 */
function getOptions(options) {
   
    const defaultOptions = {
   
        sensitive: false,
        strict: false,
        exact: false
    }

    const mergeOptions = {
   ...defaultOptions, ...options
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值