蛮多同学可能会感觉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")); // 输入nullconsole.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.jsimport { 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: false,    end: false  }  return {    ...defaultOptions,    sensitive,    strict,    end: exact  }}/** *  * @param {*} matchResult  * @param {*} matchKeys  * 这个办法次要是将matchResult和matchKeys相组合最终生成一个新的params对象 */function paramsCreator(matchResult = [], matchKeys = []) {  // 首先这个matchResult是一个类数组, 咱们须要将它转换为实在数组  // 你能够应用Array.from, 也能够应用[].slice.call等办法都能够  // 而且咱们晓得matchResult的第一项是门路, 咱们是不须要的, 所以间接是slice.call更不便  const matchVals = [].slice.call(matchResult, 1);  const paramsObj = {};  matchKeys.forEach((k, i) => {    // 别忘记, 这个k是一个对象, 而咱们只须要他的name属性    paramsObj[k.name] = matchVals[i];  })  return paramsObj; // 最初将这个参数对象丢进来}export default pathMatch;

参考 前端进阶面试题具体解答

至此, 咱们的pathMacth模块就生成了, 每次调用pathMatch办法, 都会依据参数返回给咱们一个react-router中的match对象,

history库的应用

咱们晓得, 当路由匹配组件当前, react-router会向组件外部注入一些属性, 其中的match属性咱们曾经有生成的办法了, 然而locationhistory还得劳烦咱们本人写一写

其实location就是history对象身上的一个属性, 咱们搞定了location, history天然就搞定了

有个货色咱们必须搞清楚哈, history中的办法是用来帮忙咱们切换路由的, 然而咱们晓得, 咱们的router模式是有hash模式, browser(有时咱们也称其为history模式)模式, 甚至在native端有memory模式, 当模式不同的时候, history会帮咱们操作不同的中央(比方hash模式下, 操作的就是hash, browser模式下操作的就是浏览器的历史记录), 那么咱们也晓得, router是依据你引入的是BrowserRouter还是其余Router类型来断定history须要操作哪一块的, 所以咱们要做的事就是要搞出这个BrowserRouter, 没问题吧, 因为代码量可能比拟多, 然而原理都统一, 我就不写HashRoutermemoryRouter

而在react-router中他也是强依赖了咱们下面说到的第三方库: history

咱们先来看看history库的应用, 可能下一篇博客咱们会间接去书写他的原理, 这个库不像path-to-regexp, 他的原理还是很重要的, 这篇博客因为篇幅问题也就不写history库的源码了

这个库次要实现的性能就是一个: 给你提供创立不同地址栈的history api

说的更简略一点, 就是咱们调用这个库具名导出的办法, 再通过一系列包装, 咱们就能够间接生成react-router上下文中提供的history对象

咱们能够间接来用一用这个库

import { createBrowserHistory } from "history"; // 导入一个创立操作浏览器history api的函数// 这个函数还能够接管一个配置对象, 你也能够不传// createBrowserHistory(config?);const history = createBrowserHistory({  // basename配置用于设置基门路, 大部分状况下, 咱们网站的根门路是/  // 所以咱们少数状况下不思考basename, 假如你须要思考的话, 就在这填就好了  // 填写这个的结果就是: 比方你填写basename为/news, 当前你拜访/news/details  // 的时候你的pathname就会被解析成/details  basename: "/",   forceRefresh: false, // 示意是否强制刷新页面, history api是不会刷新页面的, 而如果设置该属性为true当前,   // 则你调用push等办法的时候会间接数显页面  keyLength: 6, // location对象应用的key值长度(key值用来确定唯一性, 比方你同时拜访了同一个path, 如果没有key值的话就出问题了)  getUserConfirmation: (msg, cb) => cb(window.confirm(msg)), // 用来确定用户是否真的须要跳转(然而必须设置history的block函数并且页面真正进行跳转才会触发)});console.log("history");

输入后果如下, 咱们会发现, 他其实曾经和咱们在react中应用BrowserRouter提供的上下文对象中的history对象差不多了, 然而还有轻微的区别, 咱们先来看看这个history对象中成员的逻辑断定计划, 这对咱们后续写他的源码有用途

须要留神的中央就是: 同学不要感觉这个是window.locationwindow.history的联合哈, 这个是history本人生成的对象, 他对立面的属性很多都是通过包装的, 别搞混同了, 后续源码咱们会理解的更清晰一点

  • action: action代表的是以后地址栈最初一次操作的类型, 对于action咱们须要留神的点如下:

    • 首次通过createBrowserHistory创立的时候action固定为POP
    • 如果调用了historypush办法, action变为PUSH
    • 如果调用了historyreplace办法, action变为REPLACE
  • push: 向以后地址栈指针地位入栈一个地址
  • go: 管制以后地址栈指针偏移, 如果是0则地址不变(咱们晓得浏览器的history.go(0)会刷新页面),负数后退, 正数退后
  • goBack: 相当于go(-1)
  • goForwar: 相当于go(1)
  • replace: 替换指针所在的地址
  • listen: 这是react-router实现从新渲染页面的要害, 这个函数用于监听地址栈指针的变动, 该函数接管一个函数作为参数, 示意地址发生变化当前的回调, 回调函数又接管两个参数(location对象, action), 他返回一个函数用于解除监听, 后续咱们用到的时候我置信你就懂了
  • location对象: 表白以后地址栏中的信息
  • createHref: 传递一个location对象进去,他依据location的内容给你生成一个地址
  • block: 设置一个阻塞, 当用户跳转页面的时候会触发该阻塞, 同时该阻塞的信息参数会被传递到getUserConirmation

RouterBrowserRouter的实现

下面说了这么多, 次要都是在跟大家聊path-to-regexphistory库, 这里咱们要正式实现Router组件了

在React中, Router组件是用来提供上下文的, 而BrowserRouter创立了一个管制浏览器history apihistory对象当前而后传递给Router

咱们在react-router中新建一个文件Router.js, 同时咱们新建一个RouterContext.js, 用于存储上下文
// react-router/RouterContext.jsimport { createContext } from "react";const routerContext = createContext();routerContext.displayName = "Router";export default routerContext;
// 咱们晓得: 这个Router组件是肯定须要一个history对象的, 他不论history对象是怎么来的, 然而必须通过属性传递给他import React, { useState, useEffect } from "react";import pathMatch from "./pathMatch";import routerContext from "./RouterContext";/** * Router组件要做的事件就只有一个: 他要提供一个上下文 * 上下文中的内容有history, match, location *  * 咱们晓得创立history的时候, 有createBrowserHistory, createHashHistory等 * 所以咱们在Router里怎么都不能写死, 咱们把history作为属性传递过去 * 而在内部咱们在依据不同的组件来创立不同的history传递给Router组件,  * React也是这么做的 * @param {*} props  */export default function Router(props) {  // 咱们在Router中写的逻辑如下:  // 1. 将match对象, location对象和history对象都拿到而后进行拼凑  // 2. 如果一旦页面地址发生变化, Router要从新渲染以响应变动, 怎么响应, 就是通过listen办法  // 为什么要将location变成状态, 次要是因为当咱们的页面地址产生变动的时候, 咱们须要做的事件有几个  // - 将history里action的状态进行变更, 比方go 要变成POP, push要变成PUSH, 如果咱们没有本人的状态  //   那么咱们没有中央能够批改这个location了  // - 当页面地址发生变化的时候, 咱们须要从新渲染组件, 咱们能够应用listen来监听, 然而从新渲染组件咱们  //   能够应用本人封装一个forceUpdateHook来解决, 然而如果有了location状态, 能够一石二鸟不是更好  const [locationState, setLocationState] = useState(props.history.location);   const [action, setAction] = useState(props.history.action);  useEffect(() => {    const removeListen = props.history.listen(({location, action}) => {      // 当每次页面地址发生变化的时候, 我这边都心愿可能监听到, 监听到了当前我要从新刷新组件      setLocationState(location)      setAction(action);    })    return removeListen;  }, [])  const match = pathMatch("/", props.history.location.pathname);  return (    <routerContext.Provider value={{      match,      location: locationState,      history: {        ...props.history,        action      }    }}>      { props.children }    </routerContext.Provider>  )}
Router组件实现了还不够, 咱们须要去编写BrowserRouter.js组件
src下新建一个react-router-dom文件目录, 新建文件index.jsBrowserRouter.js
// index.jsexport { default as BrowserRouter } from "./BrowserRouter.js";
// BrowserRouter.js// BrowserRouter要做的事件非常简单, 创立一个能够管制history api的history对象// 作为属性传递给Router组件import React from "react";import Router from "../react-router/Router.js";import { createBrowserHistory } from "history";export default function BrowserRouter(props) {  const history = createBrowserHistory(props);  return (    <Router history={history}>      { props.children }    </Router>  )}
至此咱们的BrowserRouter组件也写完了

Route组件的实现

Route组件次要是用来依据不同的门路匹配不同的组件的, 其实他没那么简单, 就是通过不同的门路来渲染不同的组件, 如果你写的粗率一点, 齐全能够应用if else 来始终进行判断也能够写好Route组件, 那咱们话不多说, 来看看Route组件的实现过程吧

咱们在react-router中建设Route.js文件

import React from "react";import pathMatch from "./pathMatch";import routerContext from "./RouterContext";// 首先咱们必须要搞清楚一些流程上的货色:// 1. Route组件上会有一些属性如下:// - path// - children// - component// - render// - sensitive// - strict// - exact// 在chilren, component, render中又有一些逻辑规定如下:// children: 只有你给了children属性值, 那么无论该路由是否匹配胜利chilren都会显示// render: 一旦匹配胜利, 执行的渲染函数// component: 一旦匹配胜利, 会渲染的component// 三个的优先级: children > render > component// 当然你能够应用propTypes来束缚一些props, 也能够应用ts来束缚// 我就不束缚了, 懒一点哈哈export default function Route(props) {  // 作为Route组件, 他身上也有history, location和match对象  // 你能够本人从新来组装这些对象, 然而我认为没必要, 咱们间接  // 应用上下文里的数据就好, 只不过match对象咱们倒是的确要从新  // 匹配一下  return (    <routerContext.Consumer>      { value => {          const { location, history } = value; // 间接从上下文里解构出location, history          const { sensitive = false, exact = false, strict = false } = props;          const match = pathMatch(props.path, location.pathname, {            sensitive,            exact,            strict          })          const ctxValue = {            location,            history,            match          }          // 这个时候咱们要讲新的数据持续共享上来, 间接在提供一次Provider不就好了          return (            <routerContext.Provider value={ctxValue}>              { getRenderChildren(props.children, props.render, props.component, ctxValue) }            </routerContext.Provider>          )      } }    </routerContext.Consumer>  )} /** * 依据肯定的匹配逻辑来渲染该渲染的元素 * 这就是Route组件的外围性能 */function getRenderChildren(children, render, component, ctxValue) {  // 依据咱们之前的逻辑, 咱们晓得一旦children属性有值, 那不用说间接疏忽其余值  if( children != null ) {    // chilren咱们晓得是能够写函数的, 写成函数的话能够获取上下文的值     return typeof children === "function" ? children(ctxValue) : children;  }  // 如果children没有值, 就要看是否匹配了, 如果没有匹配间接  if( ctxValue.match == null ) return null;  // 这个时候代表匹配上了, 匹配上了如果有render就间接运行render  if( typeof render === "function" ) return render(ctxValue);  // 最初渲染component  if( component ) {    let Component = component;    // 咱们晓得: 在被匹配的组件中也是有location, history, match等属性的    return <Component {...ctxValue}/>  }  // 最初代表他component都没有  return null; // 仍旧给他来null就好了}

其实咱们这里咱们跟react-router还有一点区别, 当他的Route组件path没有的时候, 他也会间接渲染所匹配的组件, 我这里没有写, 为什么呢, 因为我感觉他这样不合逻辑, 你path都没给我我凭什么帮你渲染, 我为什么要提这一点哈, 因为我认为咱们去学习一个框架或者一个货色的时候, 要带着本人的思维逻辑去学(比方他为什么要这样做, 如果是你你会怎么做), 他不肯定是对的, 你也不肯定是错的, 你晓得了他的逻辑, 如果你感觉不合理, 你肯定要保留本人的逻辑, 这样能力防止做学习机器, 而且能够锤炼咱们的思维能力

至此Route组件曾经实现

SwitchRedirect的实现

Switch的性能实现其实非常简单, 因为咱们须要将Swicth包裹在Route组件里面, 所以咱们认真想想这个逻辑应该很快就进去了, 咱们只有在Switch里将children属性挨个遍历而后管制渲染就能够了, 咱们从react-router官网的逻辑也能够想到大略是这么回事: 因为你应用了官网Switch当前匹配不上的组件都不会在React组件树里存在

咱们在react-router目录下新建一个Switch.js

// react-router/Swicth.jsimport React from "react";import routerContext from "./RouterContext";import pathMatch from "./pathMatch";export default function Swicth(props) {  // 咱们要做的事件就是: 将props中的children挨个拿出来看, 而后如果哪一个的path门路和以后门路相匹配了  // 就渲染, 而且一旦渲染了一个, 前面的都不会再渲染了  // 那么咱们怎么晓得以后门路呢, 是不是又要用到上下文  return (    <routerContext.Consumer>    {      value => {        const { location } = value;        const {children = {}} = props;        // 这个时候咱们把children拿进去遍历, 然而遍历之前咱们要晓得, children可能会是多个状况        // 1. 是数组: 证实传了多个react元素进来, 咱们不论        // 2. 是对象: 证实只传了一个进来, 咱们要将他变成数组        // 当然还有一些细节解决, 然而因为咱们不是做产品级, 没必要搞的那么巨细无遗尴尬本人        let resultChildren = [];        if( children instanceof Array ) resultChildren = children;        else if( children instanceof Object ) resultChildren = [children];                for( const item of resultChildren ) {          const { path = "", exact = false, sensitive = false, strict = false, component: Component = null } = item.props;          // 咱们晓得location.pathname是正儿八经的浏览器地址, 而咱们书写在Route组件上的是path规定          // 所以咱们要匹配只能应用咱们之前封装好的pathMatch函数          const match = pathMatch(path, location.pathname, {            exact,            sensitive,            strict          })          // 只有不等于null就是匹配到了          if( match != null ) {            console.warn("i am warning");            return Component == null ? Component : <Component />          }        }        // 如果循环了一轮都没有匹配到        return null;      }    }    </routerContext.Consumer>  )}

Swicth组件就实现了, 其实这些组件并不是很难, 你只有顺着他的逻辑去捋一捋, 肯定是能够实现的

当初咱们要做的就是去实现咱们的Redirect组件, 在react-router目录下新建一个Redirect.js

// react-router/Redirect.js// Redirect组件其实就是用来做重定向的, 其实逻辑也能够非常简单, 当你遇到了Redirect组件, 你通过location上// 的replace办法将他去渲染指定的门路就行了import React from "react";import routerContext from "./RouterContext";import pathMatch from "./pathMatch";export default function Redirect( props ) {  console.log("我匹配上了")  // 咱们晓得Redirect会承受以下的属性  // 1. from: 代表匹配到的门路  // 2. to: 代表匹配到门路当前要去的门路, 如果to为一个对象的话, 外面是能够带参数的        // - pathname: 匹配到当前要去的门路        // - search: 就是一般的search        // - state: 就是你要附加的一些状态        // pathname是对象的模式我就懒得写了, 其实你也是去解析他的pathname顺便把参数作为属性丢过来就行了  // 3. push: 代表是否应用history.push来解决(因为他默认会应用replace)  // 其余的就是Route该有的属性: exact, sensitive, strict  const { from = "", to = "", push = false, exact = false, sensitive = false, strict = false } = props;  // 这个时候咱们要拿from来和以后的location进行比拟所以又要用到上下文  return (    <routerContext.Consumer>      {        ({location, history}) => {          console.log("props", props);          const match = pathMatch(from, location.pathname, {            strict,             sensitive,             exact          })          if( match != null ) {            // 代表匹配上了, 匹配上了咱们要做的事件就是将他推去相应的组件            console.log("to", to);            // 因为history.push如果你不放入异步队列的话, 这个时候listen事件            // 可能还没有初始化结束, 而后他就监听不到了, 我的了解是这样            // 如果有其余了解的话欢送沟通            setTimeout(() => {              history.push(to)             }, 0)          }          // 如果没有匹配上, 那就啥都不干呗          return null;        }      }    </routerContext.Consumer>  )}
至此, redirect组件也实现了

withRouter的实现

这个是一个hoc, 他的作用非常简单, 就是将路由上下文作为属性注入到组件中

咱们在react-router目录下新建一个withRouter.js

import React from "react";import routerContext from "./RouterContext";export default function(Comp) {  // 他承受一个Comp作为参数, 返回一个新的组件  function newComp(props) {    return (      <routerContext.Consumer>        {          values => (<Comp {...props} {...values}/>)        }      </routerContext.Consumer>    )  }  // 设置显示的名字这个没什么好说的吧  newComp.displayName = `withRouter(${Comp.displayName || Comp.name})`;  return newComp;}

LinkNavLink实现

写完这个LinkNavLink我根本也瘫痪了, 不过好在终于要写完了, LinkNavLink自身也不难

如果要说简略一点, 就写个a元素阻止默认事件而后应用history.push跳转就行了, 毕竟人家也就实现了一个无刷新跳转的性能

咱们在react-router-dom里新建一个Link.js

// react-router-dom/Link.jsimport React from "react";import routerContext from "../react-router/RouterContext";export default function Link(props) {  const {to, ...rest} = props;  return (    <routerContext.Consumer>      {value => {        return (          <a href={to} {...rest} onClick={e => {            e.preventDefault();            // 这里我就简略写了, 其实咱们晓得还要思考to为对象一些状况            // 而且还有to须要传参的一些状况, 这个时候就是你要写一些函数来帮忙你解析字符串或者解析对象            // 其实有些时候还要思考basename的状况, 所以最好用history.createHref来生成地址比拟好            // 还有就是依据一个参数是否是replace还是push            // 不过外围原理就是这个, 其余的细节我就不思考啦            // 有想法的同学能够本人欠缺一下            value.history.push(props.to);          }}>            { props.children }          </a>        )      }}    </routerContext.Consumer>  )}

NavLink这个组件不用说了吧, 其实就是只有location匹配上了, 他就给你加个类名就完事了

咱们在react-router-dom下新建一个NavLink.js
// react-router-dom/NavLink.jsimport React from "react";import Link from "./Link";import routerContext from "../react-router/RouterContext"import pathMatch from "../react-router/pathMatch";export default function(props) {  const {activeClass = "active", to = "", ...rest} = props;  return (    <routerContext.Consumer>      { value => {        const match = pathMatch(to, value.location.pathname, )        console.log("match result", match);        return (          <Link to={to} className={match ? activeClass : ""}  {...rest}>{ props.children }</Link>        )      } }    </routerContext.Consumer>  )}
至此LinkNavLink咱们也写完了, 然而LinkNavLink还有十分多须要欠缺的中央, 我也只是输入了外围原理, 大家有想法能够本人补充

聚合api

咱们晓得 , 咱们在react-router中引入代码都是间接在react-router-dom中引入各种组件的, 这个也不难咱们具名导出一下就好

// react-router-dom/index.jsexport { default as Redirect } from "../react-router/Redirect";export { default as Route } from "../react-router/Route";export { default as Router } from "../react-router/Router";export { default as Switch } from "../react-router/Switch";export { default as withRouter } from "../react-router/withRouter";export { default as Link } from "./Link";export { default as NavLink } from "./NavLink";

这样就没故障了

至此, 完结, 心愿可能有大手子点拨指教0.0