Hi, Everyone, 好久不见, 其实想了很久, 到底要不要出一篇
react-router
的源码级博客, 怕自己的理解不够深, 误导了大家, 但是本着书写学习笔记的习惯, 笔者还是写下来了自己对react路由的理解, 如果有问题之处还请大牛指教, 也希望这篇博客可以帮助到正在学习router
原理的你
本博客不会过度的去分析react
自身的源码, 因为这些我相信大家从git
上可以很轻易的拿到, 笔者是通过从0书写一个自己的react-router
来实现跟react
同样功能的方式来分享整个路由的思想, 这样也更好的让初入源码学习的同学更好的适应
react-router
团队本身在实现router
的时候引用了两个比较小的库, 一个叫做path-to-regexp
, 一个叫做history
, 所以笔者这里也将会直接引用这两个库, 当然势必对帮助大家对这两个库进行一个全面的熟悉和了解, 如果想了解这两个库是怎么写的, 可以移步笔者的另外的博客进行了解
目录结构
-
path-to-regexp
的了解
-
history
的了解
-
Router
的实现
-
Route
的实现
-
Switch
的实现
-
withRouter
的实现
-
Link
和NavLink
的实现
在前期的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); // 输出的就是一套正则表达式
输出结果如下
返回的正则表达式就好像是说把我们的
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
中你会随着使用更加的清晰
3. Router组件的实现
害, 终于进入正题了, 一顿操作猛如虎, 全从Router
开始撸
想要实现Router
, 我们得先知道Router
做了哪些事
- 这哥们本身不做任何的展示, 仅提供路由模式的配置
- 该组件会提供一个上下文, 上下文会提供一些使用的属性和方法, 供其他相关组件使用
- 浏览器中
Router
本身分为以下两种形式- HashRouter: 使用
HashRouter
模式匹配路由 - BrowserRouter: 使用
BrowserRouter
模式展示路由
- HashRouter: 使用
来吧, 来看个实例帮你们整体回忆一下
// 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
结构
如果图片不够清晰, 或者你看不懂也没关系, 笔者再帮你分析一波
BrowserRouter
当我们引入以后, 其实他内部是还用到了一个核心的Router
组件Router
组件得到一个属性history
为一个对象, 就是我们用history
库构造出来的对象一模一样Router
组件有一个状态location
, 该location
其实就是history
属性中的location
, 只是提出来作为属性而已Router
提供一个上下文, 里面携带一个value
属性, 为一个对象, 对象中内容如下history
: 来自于Router
组件中的history
属性, 保存了当前浏览器历史记录栈的一些方法和信息location
: 来自于Router
组件的location
状态, 保存了当前路由的一些信息match
: 用来判定当前路由跟我们之后要书写的Route
组件的上的path
规则的校验, 它来自于我们自己书写,match
对象携带以下几个属性- isExact:
Boolean
, 是否精确匹配 - params:
- isExact:
Router
的children
会被渲染进页面
这上面的这些基本使用方法我就不再过多的分析了, 别来看
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, 并进浏览器看一下结构
结构很明显已经出来了, 只是之前说好的属性都没有, 那我们得给他啊
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>
)
}
}
...
其实我们目前知道
history
和location
最终一定是从父组件来的, 那么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};
return {
end: mergeOptions.exact,
sensitive: mergeOptions.sensitive,
strict: mergeOptions.strict
}
}
...
OK,
getOptions
方法书写完以后, 我们要在pathMatch
中进行调用
// pathMatch.js
...
export default function pathMatch(path, pathname, options) {
const opts = getOptions(options);
const keys = [];
// 调用pathToRegExp方法, 传入对应的参数, 得到一个正则表达式
const reg = pathToRegExp(path, keys, opts);
// 使用正则表达式来直接匹配传递进来的pathname真实路径
const validateResp = reg.excu(pathname);
// 如果匹配结果为空, 代表当前真实路径不符合path属性规则, 直接抛出null
if(validateResp == null) return null;
// 如果匹配结果不是空, 代表有东西, 下面这套流程你玩过正则就懂
const slicedValidateResp = Array.from(validateResp).slice(1);
// 最后我们要去拼params对象, params对象的作用就是
// 假如你的地址规则是/news/:id, 传进去/news/1
// 那么params对象应该为{id: 1}
// 所以我们通过调用getParams方法来获得这个params对象
const params = getParams(slicedValidateResp, keys);
// 最后集合成一个match对象返回出去
return {
params,
isExact: pathname === validateResp[0], // 是否精确匹配一定要真实路径是不是等于匹配结果的第一位(匹配结果第一位是浏览器path路径)
path,
url: validateResp[0]
}
}
/**
* 通过传递进来的value的数组和key的数组将其包装成一个对象, {key: value}形式
* @param {*} group
* @param {*} keys
*/
function getParams(group, keys) {
const resp = {}; // 最后要丢出去的对象
for(let i = 0, len = group.length; i < len; i++) {
const val = group[i];
const name = keys[i].name;
resp[name] = val;
}
return resp;
}
function getOptions(options) {...}
...
我们已经完成了
pathMatch.js
的编写, 也可以很轻易的通过pathname
,path
,options
获得一个params对象, 那就来吧, 继续回到Router.js
// Router.js
...
import pathMatch from './pathMatch.js';
export default class Router extends React.PureComponent {
...
render() {
const contextValue = {
...
// 在处理根路径的时候, react是直接通过/写死的, 所以我们也写死
match: pathMatch('/', this.state.location.pathname)
}
...
}
}
...
到了最后一步了, 虽然我们的
location
在state里, 但是其实如果用户通过手段跳转了路由, 我们是没办法感知的, 所以我们要在Router.js
中加上监听
history
api提供一个listen
方法, 同时在上下文中我们已经有history
了, 所以可以直接使用该listen
方法
listen
方法: 该方法接收一个函数作为参数, 作为当路由地址发生改变的时候执行的回调函数, 同时listen
方法返回一个一个函数removeListen
, 用来解除监听listen
方法的参数函数又携带一个对象参数, 里面附带两个属性location
和action
,action
描述了当前是入历史记录栈还是出历史记录栈操作(你可以不用那么关心),location
则是当前路由的相关信息
// Router.js
export default class Router extends React.PureComponent {
...
componentDidMount() {
// 在挂载完毕以后直接开启监听, 路由一旦修改就会执行回调函数
this.removeListen = this.props.history.listen(({location, action}) => {
console.log('路由修改了', location, action);
// 回调函数执行我们直接强制重新渲染页面
this.setState({
location
})
})
}
componentWillUnmount() {
// 在组件写在前我们直接卸载监听
this.removeListen();
}
render() {
...
}
}
OK, 到此, 我们的
Router
已经写完, 可以来加把火写写BrowserRouter
, 我们在src
目录下新建react-router-dom
文件夹, 新建文件BrowserRouter.js
// BrowserRouter.js
// BrowserRouter相当的简单, 我们已经有了History库, 所以直接从该库中导入createBrowserHistory
// 然后调用Router组件并给他相应的history属性即可
import React from 'react';
import Router from '../react-router/Router';
import { createBrowserHistory } from 'history';
export default class BrowserRouter extends React.PureComponent {
history = createBrowserHistory(this.props);
render() {
return (
<Router history={this.history}>
{ this.props.children }
</Router>
)
}
}
关于
HashRouter
笔者就不写了, 方案都一样, 引入createHashHistory
就OK, 那咱来测试一下?
// App.js
import { BrowserRouter } from './react-router-dom/BrowserRouter';
import React from 'react';
export default App(props) {
return (
<BrowserRouter>
我是children
</BrowserRouter>
)
}
结构如下, 已经跟官方的Router
结构相差无几了, 该给的参数我们也都给了
4. Route组件的实现
其实Router
组件走完以后, react-router
最难的地方已经走完了, 如果你之前的没看太懂, 没关系, 等读完Router
和Route
, 你再结合例子自己再揣摩一下也差不多了, OK, 话不多说, 咱开始?
那么Route
做了哪些事呢
- 根据不同的
path
属性, 展示不同的组件 - 该组件接收几个重要的属性
参数 | 功能 |
---|---|
path | 要匹配的路径规则, 默认情况下不区分大小写 |
component | 路径匹配成功以后要渲染的组件 |
sensitive | 开启区分路径大小写 |
exact | 开启路径精确匹配 |
render | 路径匹配成功以后执行的render props |
- 如果不书写
path
属性, 则一定会渲染出组件
同样, 整个实例帮你们回忆一下
// App.js
import { BrowserRouter as Router, Route } from 'react-router-dom';
function Page1(props) {
return (
<div>
我是Page1
<button onClick={() => props.history.push('/page2')}>去Page2</button>
</div>
)
}
function Page2(props) {
return (
<div>
我是Page2
<button onClick={() => props.history.push('/page1')}>去Page1</button>
</div>
)
}
export default function App(props) {
return (
<Router>
<Route path='/page1' component={Page1}></Route>
<Route path='/page2' component={Page2}></Route>
</Router>
)
}
整个代码最后的结果如下, React Devtools
中的结果也如下
如果图片不够清晰, 或者你看不懂也没关系, 笔者再帮你分析一波
- 我们是可以看到首先
Route
组件成为了路由上下文的消费者, 之前我们知道Router
是上下文的提供者 - 作为路由的消费者,
Route
组件得到了上下文中的value
属性毋庸置疑(history
,location
,match
) - 我们又发现,
Route
组件又提供了一个Provider
, 他为什么要又提供一个上下文呢, 原因还是比较简单, 因为我们知道history
和location
都没有问题, 但是match
使我们在根组件写死的吧, 因为那个时候我们根本没办法得到路径规则, 路径规则是Route
上的path
属性, 所以在这里我们要重新提供一次上下文, 并修改match
属性 - 如果
Route
组件出现children
属性, 则不管path
匹不匹配都会渲染相应的children
- 如果
Route
路径匹配, 则优先渲染render
属性, 最后再component
属性
来吧, 根据上面的条数, 开始撸, 在
react-router
目录下新建一个Route.js
// Route.js
import React from 'react';
import { default as ctx } from './RouterContext.js';
export default class Route extends React.PureComponent {
// 封装一个新的getMatch方法
getMatch = location => {
const { pathname } = location;
const { path='/', exact = false, sensitive = false, strict = false } = this.props;
return pathMatch(path, pathname, {
exact,
strict,
sensitive
})
}
// 上下文处理函数
consumerHandler = value => {
// 在这里, 我们要处理一下match规则
this.ctxValue = {
history: value.history,
location: value.location,
match: this.getMatch(value.location), // 获取新的match对象
}
return <RouterContext.Provider value={ this.ctxValue }>
{ this.renderChildren(this.ctxValue) }
</RouterContext.Provider>
}
// 根据不同的规则渲染不同的属性
renderChildren = ctx => {
if(this.props.children !== undefined && this.props.children !== null) {
if(typeof this.props.children === 'function') return this.props.children();
else return this.props.children;
}else if(!ctx.match) {
return null;
}else if(typeof this.props.render === 'function') {
return this.props.render(ctx);
}else if(this.props.component) {
const Component = this.props.component;
return <Component {...ctx} />
}else {
return null;
}
}
render() {
{/*作为上下文的消费者, Route组件一定由Consumer包装*/}
<ctx.Consumer>
{ this.consumerHandler }
</ctx.Consumer>
}
}
因为
Router
比较简单, 笔者直接就一气呵成了, 而且代码也都比较容易读懂, 那么我们来测试一下?
// App.js
import React from 'react';
// 引入自己写的BrowserRouter, 和自己写的Route
import { BrowserRouter } from './react-router-dom/index';
import { Route } from './react-router/index'
function Page1(props) {
return (
<div>
我是Page1
<button onClick={() => {
props.history.push('/page2')
}}>去Page2</button>
</div>
);
}
function Page2(props) {
return <div>
我是Page2
<button onClick={() => {
props.history.push('/page1')
}}>去Page1</button>
</div>;
}
function Page3(props) {
return <div>我是无论如何都要进行渲染的</div>
}
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path='/page1' component={Page1} />
<Route path='/page2' component={Page2} />
<Route component={Page3} />
</BrowserRouter>
</div>
);
}
export default App;
实现效果和React Devtools
如下, 至此我们已经实现了Router
和Route
组件, 并且之后的组件会更加的easy
5. Switch组件的实现
这哥们太简单了, 简单到令人发指, 他的功能也很简单
- 匹配到第一个符合
path
规则的哥们就停
不多废话, 这么简单的组件我相信你不需要演示吧? 直接一气呵成
import React from 'react';
import RouterContext from './RouterContext';
import pathMatch from './pathMatch';
export default class Switch extends React.PureComponent {
getRenderChild = ({ location }) => {
let lastChildren = [];
if (this.props.children instanceof Array) {
lastChildren = this.props.children;
} else if (typeof this.props.children === 'object') {
lastChildren = [this.props.children];
}
for (let child of lastChildren) {
const { path = '/', exact = false, strict = false, sensitive = false } = child.props;
// 将Route的path属性规则和真实pathname进行比较, 如果有返回params对象代表匹配成功
const validateResp = pathMatch(path, location.pathname, { exact, strict, sensitive });
if (validateResp != null) {
return child;
}
}
return null;
}
render() {
return (
<RouterContext.Consumer>
{ this.getRenderChild }
</RouterContext.Consumer>
)
}
}
这太简单了, 我们来自己试验一下
// App.js
import React from 'react';
// import './react-router/pathMatch';
// import './react-router/browserHistory';
// import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { BrowserRouter } from './react-router-dom/index';
import { Route, Switch } from './react-router/index'
function Page1(props) {
return (
<div>我是Page1
<button onClick={() => {
props.history.push('/page2')
}}>去Page2</button>
</div>
);
}
function Page2(props) {
return <div>我是Page2</div>;
}
function Page3(props) {
return <div>我是无论如何都要进行渲染的</div>
}
function App() {
return (
<div className="App">
<BrowserRouter>
<Switch>
<Route path='/page1' component={Page1} />
<Route path='/page2' component={Page2} />
<Route component={Page3} />
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
页面渲染结果如下, 如果没有Switch
组件, 我们知道Page3
无论如何都需要渲染的, 加上Switch
以后就不一样了, 所以这下你也明白了为什么react-router
说在写Route
的时候顺序不要乱写, 完全因为他的这种匹配机制
6. withRouter的实现
withRouter
高阶组件
这哥们的功能也非常的简单, 经过他包装以后的组件会直接享有路由上下文
不用演示了吧? 这种这么简单的功能还要演示的话完全是在侮辱我啊, 主要是懒
import RouterContext from './RouterContext';
export default function withRouter(Comp) {
function RouterWrapper (props) {
return (
<RouterContext.Consumer>
{value => <Comp {...value} {...props} />}
</RouterContext.Consumer>
)
}
RouterWrapper.displayName = `withRouter(${Comp.displayName || Comp.name})`; // 修改显示的名称
return RouterWrapper;
}
我们来看看效果
// App.js
import React from 'react';
import { BrowserRouter } from './react-router-dom/index';
import withRouter from './react-router/withRouter';
function Page1(props) {
return (
<div>我是Page1
路径: { props.history.location.pathname }
</div>
);
}
// HOC包装一层
const RouterPage1 = withRouter(Page1);
function App() {
return (
<div className="App">
<BrowserRouter>
<RouterPage1 />
</BrowserRouter>
</div>
);
}
export default App;
我们知道如果不进行高阶组件包装, 那么Page1
由于没有上下文联系, 他是生死获取不到location
的, 他一定会报错, 但是经过我们这么一包装, 你就可以看下面的结果了
7. Link和NavLink的实现
Link
Link的核心功能如下: 无刷新跳转页面, 都不想说了我们直接肝吧, 这个小家伙边写边说吧
在
react-router-dom
目录下新建Link.js
// Link.js
import React from 'react';
export default function Link(props) {
return (
<a href={to}>
{ props.children }
</a>
)
}
OK, 写完上面这块我们测试一下
// App.js
import React from 'react';
import { BrowserRouter } from './react-router-dom/index';
import { Route, Switch } from './react-router/index'
import Link from './react-router-dom/Link';
function Page1(props) {
return (
<div>我是Page1
路径:{ props.history.location.pathname}
</div>
);
}
function Page2(props) {
return <div>我是Page2</div>;
}
function Page3(props) {
return <div>
<Link to='/page1'>去Page1</Link>
<Link to='/page2'>去Page2</Link>
</div>
}
function App() {
return (
<div className="App">
<BrowserRouter>
<Switch>
<Route path='/page1' component={Page1}></Route>
<Route path='/page2' component={Page2}></Route>
<Route path='/' component={Page3}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
实现效果如下, 其实这个时候已经可以实现跳转了, 只不过是有刷新的跳转
所以说我们做的还远远不够, 我们来考虑另一些问题, 对象形式的
to
属性和无刷新跳转
import React from 'react';
import { default as ctx } from '../react-router/RouterContext';
import { parsePath } from 'history'
export default function Link(props) {
// 如果props.to是对象的话, 我们要从history库中拿出一个方法来将他变成一个路径
// 所以我们必须要拿到上下文
const {to, ...rest} = props;
return (
<ctx.Consumer>
{ value => {
const curLocation = to;
let href;
if(typeof curLocation === 'object') {
href = value.history.createHref(curLocation);
}else {
// 这里为什么要这样转一层啊, 就是通过createHref构造的href会有basename
// 而我们自己写的是没有basename的
href = value.history.createHref(parsePath(to))
}
return <a {...rest} onClick={
e => {
{/** 直接组织默认事件就无刷新跳转了 */}
e.preventDefault();
value.history.push(href);
}
} href={href}>{ props.children }</a>
} }
</ctx.Consumer>
)
}
实现效果如下, 我们已经做到了无刷新跳转和对象形式的props.to
, 那么这个Link
组件也基本OK了, 至于那些replace
之类的细节我们真的没必要探究, 因为他太简单了, 你都只需要判断一下replace
属性有没有传true
, true
的话就用history.replace
跳转
NavLink
NavLink无非就是添加了一个类名, 特别是我们在写好了Link
以后, NavLink
就好写多了
在
react-router-dom
目录下新建一个NavLink.js
// NavLink.js
import React from 'react';
import Link from './Link';
import { default as ctx } from '../react-router/RouterContext';
import { parsePath } from 'history';
import pathMatch from '../react-router/pathMatch';
export default function NavLink(props) {
// 我们是怎么判断当前是激活路径的呢
// 也很简单, 我们通过props.to中的路径和当前浏览器中的路径进行比较
// 如果一致当前就是激活路径了
const { activeClass = 'active', sensitive = false,
exact = false, strict = false, ...rest } = props;
return (
<ctx.Consumer>
{ ({location}) => {
let loc = props.to;
if(typeof props.to == 'string') {
// 如果当前的props.to是字符串, 我们是要将他们变成对象的
loc = parsePath(props.to);
}
// 拿去校验, 我们之前写好的方法, 校验成功是会给我们返回params对象的
const validateResp = pathMatch(loc.pathname, location.pathname, {sensitive, exact, strict});;
if(validateResp) {
console.log('adasdasdasdas');
return <Link {...rest} className={activeClass}></Link>
}else {
return <Link {...rest}></Link>
}
} }
</ctx.Consumer>
)
}
我们来测试一下
import React from 'react';
import { BrowserRouter } from './react-router-dom/index';
import { Route, Switch } from './react-router/index'
import NavLink from './react-router-dom/NavLink';
function Page1(props) {
return (
<div>我是Page1
路径:{ props.history.location.pathname}
</div>
);
}
function Page2(props) {
return <div>我是Page2</div>;
}
function Page3(props) {
return <div>
<NavLink to={{
pathname: '/page1',
search: '?a=1&b=2'
}}>去Page1</NavLink>
<NavLink to='/page2'>去Page2</NavLink>
</div>
}
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path='/page1' component={Page1}></Route>
<Route path='/page2' component={Page2}></Route>
<Route path='/' component={Page3}></Route>
</BrowserRouter>
</div>
);
}
export default App;
实现效果如下, 我们已经可以根据不同的路由地址切换不同的
active
了
OK, 至此我们Link
和NavLink
也全部写完了
终于7个大个都给我整完了, 感谢观看, 如有问题, 希望赐教