蛮多同学可能会感觉react-router
很简单, 说用都还没用明确, 还从0实现一个react-router
, 其实router
并不简单哈, 甚至说你看了这篇博客当前, 你都会感觉router
的外围原理也就那么回事
至于react-router
帮忙咱们实现了什么货色我就不过多论述了, 这个间接移步官网文档, 咱们上面间接聊实现
另外: react-router
源码有依赖两个库path-to-regexp
和history
, 所以我这里也就间接引入这两个库了,尽管上面我都会讲到根本应用, 然而同学有工夫的话还是能够浏览以下官网文档
还有一个须要留神的点是: 上面我书写的router
原理都是应用hooks
+ 函数组件来书写的, 而官网是应用类组件书写的, 所以如果你对hooks
还不是很明确的话, 得去补一下这方面的常识, 为什么要抉择hooks
, 因为当初绝大多数大厂在react
上根本都在鼎力举荐应用hook
, 所以咱们得跟上时代不是, 而且我着重和大家聊的也是原理, 而不是跟官网截然不同的源码, 如果要1比1的复刻源码不带本人的了解的话, 那你去看官网的源码就行了, 何必看这篇博文了
在本栏博客中, 咱们会聊聊以下内容:
- 封装本人的生成
match
对象办法 history
库的应用Router
和BrowserRouter
的实现Route
组件的实现Switch
和Redirect
的实现withRouter
的实现Link
和NavLink
实现- 聚合
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
属性咱们曾经有生成的办法了, 然而location
和history
还得劳烦咱们本人写一写
其实location
就是history
对象身上的一个属性, 咱们搞定了location
, history
天然就搞定了
有个货色咱们必须搞清楚哈,history
中的办法是用来帮忙咱们切换路由的, 然而咱们晓得, 咱们的router
模式是有hash
模式,browser
(有时咱们也称其为history
模式)模式, 甚至在native端有memory
模式, 当模式不同的时候,history
会帮咱们操作不同的中央(比方hash
模式下, 操作的就是hash
,browser
模式下操作的就是浏览器的历史记录), 那么咱们也晓得,router
是依据你引入的是BrowserRouter
还是其余Router类型来断定history
须要操作哪一块的, 所以咱们要做的事就是要搞出这个BrowserRouter
, 没问题吧, 因为代码量可能比拟多, 然而原理都统一, 我就不写HashRouter
和memoryRouter
了
而在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.location
和window.history
的联合哈, 这个是history
本人生成的对象, 他对立面的属性很多都是通过包装的, 别搞混同了, 后续源码咱们会理解的更清晰一点
action: action代表的是以后地址栈最初一次操作的类型, 对于action咱们须要留神的点如下:
- 首次通过
createBrowserHistory
创立的时候action
固定为POP
- 如果调用了
history
的push
办法,action
变为PUSH
- 如果调用了
history
的replace
办法,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
中
Router
和BrowserRouter
的实现
下面说了这么多, 次要都是在跟大家聊path-to-regexp
和history
库, 这里咱们要正式实现Router
组件了
在React中, Router
组件是用来提供上下文的, 而BrowserRouter
创立了一个管制浏览器history api
的history
对象当前而后传递给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.js
和BrowserRouter.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
组件曾经实现
Switch
和Redirect
的实现
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;}
Link
和NavLink
实现
写完这个Link
和NavLink
我根本也瘫痪了, 不过好在终于要写完了, Link
和NavLink
自身也不难
如果要说简略一点, 就写个
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> )}
至此Link
和NavLink
咱们也写完了, 然而Link
和NavLink
还有十分多须要欠缺的中央, 我也只是输入了外围原理, 大家有想法能够本人补充
聚合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