1.什么是路由?
在web应用开发中,路由是非常重要的一部分,在浏览器当前的URL发生变化时,路由系统会做出一些响应,用来保证用户界面与URL同步。随着单页面 应用的到来,为之服务的第三方库也相继出现了,现在的主流框架也都有自己的路由,比如React,Vue,Angular等,那react-router相对于其他路由 系统又有做了哪些优化呢?他是如何利用React的UI状态机制特性呢?接下来我们将以阅读源码的形式去解读一下React的路由系统。
2.react-router和react-router-dom
我们在写React路由的时候一般都是引入react-router-dom这个库,那么它和react-router有什么关系呢?
react-router其实不只是为react服务,他还支持在react-native下使用,我们打开它的github仓库可以看到 很多个库
- react-router 提供了一些router的核心api,包括Router, Route, Switch等,但是它没有提供dom操作进行跳转的api。
- react-router-dom 用于Browser端的路由系统,依赖react-router
- react-router-native 用于RN端的路由系统,依赖react-router
- react-router-config 是一个辅助插件,帮我们更好的管理路由,依赖react-router
其实react-router-dom是专门用于Browser端,他依赖react-router,内部继承了react-router所有的组件,并且在react-router基础之上多封装了几个
3.基本使用
第一步 定义路由表
import {Redirect} from '../node_modules_copy/react-router-dom/modules';//引入重定向组件
import RouterPage from '../pages/router/Router';
import RouterListPage from '../pages/router/pages/list/List';
import RouterDetailPage from '../pages/router/pages/detail/Detail';
// src/router/index.js
// 定义路由表
export default [
{
path: '/router',
component: RouterPage,
title: '模拟根路由',
routes: [
{
path: '/router/list',
component: RouterListPage,
exact: true,
title: '列表',
},
{
path: '/router/detail',
component: RouterDetailPage,
exact: true,
title: '详情',
}
]
},
{
path: '/', //当路径匹配到'/'时将重定向到'/router' 路由
component: () => <Redirect to='/router'/>
},
]
复制代码
第二步 在App.js中引入并构建路由
import {BrowserRouter,HashRouter} from './node_modules_copy/react-router-dom/modules';
import {renderRoutes} from './node_modules_copy/react-router-config/modules'; //引入renderRoutes方法
import routers from './router'; //引入我们刚刚定义的路由表
//BrowserRouter history路由模式
//HashRouter hash路由模式
//renderRoutes 用来渲染我们的路由表
function App() {
return (
<BrowserRouter>
{/*渲染路由表,第二个参数可以混入props*/}
{renderRoutes(routers,{value:'上面传过来的'})}
</BrowserRouter>
);
}
export default App;
复制代码
第三步 定义页面
// /src/pages/router/Router.js 模拟跟路由页
import React, {Component} from 'react';
import {NavLink} from "../../node_modules_copy/react-router-dom/modules";
import {renderRoutes, matchRoutes} from "../../node_modules_copy/react-router-config/modules";
const navList = [
{
to: '/router/list',
title: '去列表'
},
{
to: '/router/detail',
title: '去详情 '
}
]
const styles = {
padding: "20px",
margin: "20px",
width: "300px",
border: "solid 1px #ccc"
}
class RouterPage extends Component {
render() {
return (
<div>
<ul>
{
navList.map((item, index) => {
return <li key={index}>
<NavLink
to={item.to}
activeStyle={{color: 'red'}}>
{item.title}
</NavLink>
</li>
})
}
</ul>
<div className={'container'} style={styles}>
{renderRoutes(this.props.route.routes)}
</div>
</div>
);
}
}
export default RouterPage;
// /src/pages/router/pages/list 列表页
const List = () => {
return (
<div>
<h1>list</h1>
</div>
);
};
export default List;
// /src/pages/router/pages/detail 详情页
const Detail = () => {
return (
<div>
<h1>detail</h1>
</div>
);
};
export default Detail;
复制代码
最终效果
4.剖析react-router-dom
入口文件
export {
MemoryRouter,
Prompt,
Redirect,
Route,
Router,
StaticRouter,
Switch,
generatePath,
matchPath,
withRouter,
useHistory,
useLocation,
useParams,
useRouteMatch
} from "../../react-router/modules"; //这些组件全部都是从react-router中原封不动的拿过来的,没做任何处理
export { default as BrowserRouter } from "./BrowserRouter.js"; //如果想使用hash路由模式则使用此组件
export { default as HashRouter } from "./HashRouter.js"; //如果想使用history路由模式则使用此组件
export { default as Link } from "./Link.js"; // Link组件,其实就是对a标签做了处理,用于跳转页面
export { default as NavLink } from "./NavLink.js"; // NavLink组件,二次对Link做了处理,多用于导航
复制代码
react-route-dom内置了所有react-router的方法
- BrowserRouter history路由
- HashRouter hash路由
- Link 跳转页面的组件
- NavLink 跳转页面的导航组件
4.1 BrowserRouter和HashRouter
//react-router-dom/modules/utils/BrowserRouter.js
import React from "react";
import { Router } from "../../react-router/modules";
import { createBrowserHistory as createHistory } from "../../history";
/**
* The public API for a <Router> that uses HTML5 history.
*/
//从 history 引入createBrowserHistory方法用于生成history对象
// 引入了react-router的Router跟路由组件并且把history对象传给它
class BrowserRouter extends React.Component {
history = createHistory(this.props); //生成history对象
render() {
console.log(this.history)
return <Router history={this.history} children={this.props.children} />;
}
}
export default BrowserRouter;
//react-router-dom/modules/utils/HashRouter.js
import React from "react";
import { Router } from "../../react-router/modules";
import { createHashHistory as createHistory } from "../../history";
/**
* The public API for a <Router> that uses window.location.hash.
*/
class HashRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
export default HashRouter;
复制代码
其实不难看出,BrowserRouter和HashRouter的代码几乎是一模一样的,唯一不同在于引入的方法不一样,一个引入的createHashHistory一个 引入的createBrowserHistory,那么这两个方法有什么区别呢?这里不是我们的重点,就不具体讲了,有兴趣的可以看一下history这个库
4.2 Link
// react-router-dom/modules/Link.js
import React from "react";
import {__RouterContext as RouterContext} from "../../react-router/modules"; //从react-router中引入Context
import {
resolveToLocation,
normalizeToLocation
} from "./utils/locationUtils.js";
//兼容react 15版本
const forwardRefShim = C => C;
let {forwardRef} = React;
if (typeof forwardRef === "undefined") {
forwardRef = forwardRefShim;
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
const LinkAnchor = forwardRef(
(
{
innerRef, // TODO: deprecate
navigate,
onClick,
...rest
},
forwardedRef
) => {
const {target} = rest;
let props = {
...rest,
onClick: event => {
try {
//try catch捕获一下点击事件的错误
if (onClick) onClick(event);
} catch (ex) {
event.preventDefault(); //阻止默认事件
throw ex;
}
if (
!event.defaultPrevented && // 判断是否阻止了默认事件
event.button === 0 && // 判断必须是左键点击
(!target || target === "_self") && // 让浏览器处理"target=_blank"
!isModifiedEvent(event) // 忽略使用修改键的点击
) {
event.preventDefault(); //阻止默认事件
navigate(); //跳转
}
}
};
//兼容react 15版本
if (forwardRefShim !== forwardRef) {
props.ref = forwardedRef || innerRef;
} else {
props.ref = innerRef;
}
return <a {...props} />;
}
);
/**
* The public API for rendering a history-aware <a>.
*/
const Link = forwardRef(
(
{
component = LinkAnchor,
replace,
to,
innerRef, // TODO: deprecate
...rest
},
forwardedRef
) => {
return (
<RouterContext.Consumer>
{context => {
//解构出history对象
const {history} = context;
//依然是创建location对象,不过这里的location对象我们是用来做跳转的
const location = normalizeToLocation(
resolveToLocation(to, context.location),
context.location
);
//创建href,a标签跳转需要用到
const href = location ? history.createHref(location) : "";
const props = {
...rest,
href,
//因为将a标签的默认事件全部被阻止了,a标签的默认href跳转不会生效,会在navigate做跳转操作
navigate() {
const location = resolveToLocation(to, context.location);
const method = replace ? history.replace : history.push;
// 跳转的的方法
method(location);
}
};
//兼容react 15版本
if (forwardRefShim !== forwardRef) {
props.ref = forwardedRef || innerRef;
} else {
props.innerRef = innerRef;
}
//创建React组件并返回
return React.createElement(component, props);
}}
</RouterContext.Consumer>
);
}
);
export default Link;
// react-router-dom/modules/utils/locationUtils.js
import { createLocation } from "history";
export const resolveToLocation = (to, currentLocation) =>
typeof to === "function" ? to(currentLocation) : to;
export const normalizeToLocation = (to, currentLocation) => {
return typeof to === "string"
? createLocation(to, null, null, currentLocation)
: to;
};
复制代码
4.3 NavLink
// react-router-dom/modules/NavLink.js
import React from "react";
import {__RouterContext as RouterContext, matchPath} from "../../react-router/modules";
import Link from "./Link.js"; //引入Link组件
import {
resolveToLocation,
normalizeToLocation
} from "./utils/locationUtils.js";
// 兼容 react 15
const forwardRefShim = C => C;
let {forwardRef} = React;
if (typeof forwardRef === "undefined") {
forwardRef = forwardRefShim;
}
//合并class
function joinClassnames(...classnames) {
return classnames.filter(i => i).join(" ");
}
/**
* A <Link> wrapper that knows if it's "active" or not.
*/
const NavLink = forwardRef(
(
{
"aria-current": ariaCurrent = "page",
activeClassName = "active", //当路由匹配时的class名
activeStyle, //当路由匹配时的style
className: classNameProp, //class
exact,
isActive: isActiveProp, //是否匹配
location: locationProp, //location对象
sensitive,
strict,
style: styleProp, //style
to,
innerRef, // TODO: deprecate
...rest
},
forwardedRef
) => {
return (
<RouterContext.Consumer>
{context => {
//依然是获取location对象
const currentLocation = locationProp || context.location;
//将要要跳转的location对象,跳转会使用它
const toLocation = normalizeToLocation(
resolveToLocation(to, currentLocation),
currentLocation
);
const {pathname: path} = toLocation;
//将path的特殊符号进行转译
const escapedPath =
path && path.replace(/([.+*?=^!:${}()[]|/\])/g, "\$1");
//使用老方法,获取match对象
const match = escapedPath
? matchPath(currentLocation.pathname, {
path: escapedPath,
exact,
sensitive,
strict
})
: null;
//判断是否匹配
const isActive = !!(isActiveProp
? isActiveProp(match, currentLocation)
: match);
//如果匹配,将两个class合并,不匹配则只用classNameProp
const className = isActive
? joinClassnames(classNameProp, activeClassName)
: classNameProp;
//如果匹配,将两个style合并,不匹配则只用styleProp
const style = isActive ? {...styleProp, ...activeStyle} : styleProp;
const props = {
//用于标记是否匹配当前路由的属性
"aria-current": (isActive && ariaCurrent) || null,
className, //class
style, //style
to: toLocation, //要跳转的location对象
...rest
};
// 兼容react 15
if (forwardRefShim !== forwardRef) {
props.ref = forwardedRef || innerRef;
} else {
props.innerRef = innerRef;
}
return <Link {...props} />;
}}
</RouterContext.Consumer>
);
}
);
export default NavLink;
复制代码
5.剖析react-router
入口文件
// react-router/modules/index.js
export { default as MemoryRouter } from "./MemoryRouter.js";
export { default as Prompt } from "./Prompt.js";
export { default as Redirect } from "./Redirect.js";
export { default as Route } from "./Route.js";
export { default as Router } from "./Router.js";
export { default as StaticRouter } from "./StaticRouter.js";
export { default as Switch } from "./Switch.js";
export { default as generatePath } from "./generatePath.js";
export { default as matchPath } from "./matchPath.js";
export { default as withRouter } from "./withRouter.js";
import { useHistory, useLocation, useParams, useRouteMatch } from "./hooks.js";
export { useHistory, useLocation, useParams, useRouteMatch };
export { default as __HistoryContext } from "./HistoryContext.js";
export { default as __RouterContext } from "./RouterContext.js";
复制代码
5.1 Router
// react-router/modules/Router.js
import React from "react";
import HistoryContext from "./HistoryContext.js"; //HistoryContext
import RouterContext from "./RouterContext.js"; //RouterContext
/**
* The public API for putting history on context.
*/
class Router extends React.Component {
//computeRootMatch是match对象的默认值,其他组件也会用到,如果什么都匹配不到将会使用默认值
static computeRootMatch(pathname) {
return {path: "/", url: "/", params: {}, isExact: pathname === "/"};
}
constructor(props) {
super(props);
this.state = {
location: props.history.location //定义出事location对象
};
// 这是一个小技巧。我们得开始监听位置
// 如果有任何<Redirect>,构造函数中此处的更改
// 在初始渲染。如果有,他们将替换/推
// 由于cDM先于父母在孩子身上发生,我们可能会
// 在<Router>被挂载之前获取一个新的位置。
this._isMounted = false; //用于判断第一次渲染是否完成
this._pendingLocation = null; //暂存的location对象
if (!props.staticContext) { //这里不用管,staticContext默认是undefined,只有在服务端渲染情况下才有值,我们忽略就好
//这里监听路由的变化,路由变化以后使用setState进行更新,context包裹的所有组件都会进行更新
this.unlisten = props.history.listen(location => {
if (this._isMounted) { //如果第一次渲染完成,则使用setState
this.setState({location});
} else { //如果第一次渲染还未完成,先缓存一下location,意义是为了减少不必要的更新
this._pendingLocation = location;
}
});
}
}
componentDidMount() {
this._isMounted = true; //标记未初始化完成
if (this._pendingLocation) { //初始化完成以后,判断是否有缓存的location,如果有则立即更新
this.setState({location: this._pendingLocation});
}
}
componentWillUnmount() {
//这里去销毁监听函数
if (this.unlisten) this.unlisten();
}
render() {
return (
//使用context向下传递属性
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
{/*这里使用context缓存一下history对象,因为在useHistory中有用到*/}
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
}
export default Router;
复制代码
Router要做的事也很简单

第一是使用RouterContext向下去传递history、location对象等属性,使用HistoryContext去保存history对象,因为其他组件会用到,并且不是被RouterContext包裹的,所以需要缓存一下
第二是去监听路由的变化获取到新的location并使用setState触发更新,这样RouterContext下的所有组件也会更新,会根据路由重新匹配组件,从而实现路由发生变化页面做出响应的效果
5.2 Switch
// react-router/modules/Switch.js
import React from "react";
//之前我们在Router根组件里给RouterContext的value赋的值,现在引入RouterContext 里面缓存了history、location对象等属性
import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";
/**
* The public API for rendering the first <Route> that matches.
*/
class Switch extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
//获取location对象,props中有取props的,没有则取context的
const location = this.props.location || context.location;
let element, match;
//这里为什么不是Array.forEach有疑惑的可以去查一下React.Children.forEach和Array.forEach的区别
React.Children.forEach(this.props.children, child => {
//判断match是null并且child是一个React组件,注意match对象有值之后这里的if判断就进不来了,就不会继续去匹配了
//这也是是Switch的核心部分,只渲染第一个匹配的路由就是在这里做的
if (match == null && React.isValidElement(child)) {
element = child; //用element缓存child的值,循环外面要用到
const path = child.props.path || child.props.from; //获取path,这个path就是我们传给Route组件的path
//path如果可以取的到,那么他一定是一个string类型的,所以需要matchPath这个方法去做处理,帮我们转换成match对象
match = path
? matchPath(location.pathname, { ...child.props, path })
//如果获取不到则使用context中缓存match的默认值,相当于Router.computeRootMatch(location.pathname)
: context.match;
}
});
//为了保证原组件不受影响使用cloneElement克隆一个新组建return出去
return match
? React.cloneElement(element, { location, computedMatch: match })
: null;
}}
</RouterContext.Consumer>
);
}
}
export default Switch;
// react-router/modules/matchPath.js
import pathToRegexp from "path-to-regexp"; //用来生成正则的库,不了解的可以去查一下
const cache = {}; //用来缓存compilePath结果的对象
const cacheLimit = 10000; //缓存的最多个数
let cacheCount = 0; //缓存的次数
function compilePath(path, options) {
//用end、strict、sensitive相关属性做cache的key
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
//如果cache[cacheKey]有值取它的值,如果没有则给它赋值为{}
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
//判断缓存的pathCache[path]有值,则直接返回
if (pathCache[path]) return pathCache[path];
//匹配关键字的数组,如params参数传给它,它可以帮我们匹配出来
const keys = [];
//pathToRegexp的返回值是一个正则
const regexp = pathToRegexp(path, keys, options);
//result结果
const result = { regexp, keys };
//判断缓存的次数未超出最大值
if (cacheCount < cacheLimit) {
//继续缓存结果
pathCache[path] = result;
//计数器自增
cacheCount++;
}
//返回result
return result;
}
/**
* Public API for matching a URL pathname to a path.
*/
function matchPath(pathname, options = {}) {
if (typeof options === "string" || Array.isArray(options)) { //这里不用管,Switch中的matchPath没进入这个if
options = { path: options };
}
const { path, exact = false, strict = false, sensitive = false } = options; //从options中取出path,exact等相关属性
const paths = [].concat(path); //深拷贝一下
return paths.reduce((matched, path) => { //将最终match结果返回出去,初始值为null
if (!path && path !== "") return null; //path不存在或path!=="" 直接返回null
if (matched) return matched; //matched初始值为null,循环return的值会赋给matched,有值返回它,一般情况不会有值
const { regexp, keys } = compilePath(path, {
end: exact, //正则是否匹配至字符串结尾
strict, //是否严格匹配
sensitive //是否区分大小写
});
const match = regexp.exec(pathname); //用exec去匹配当前pathname,返回值是一个数组
if (!match) return null; //匹配失败,直接返回null
const [url, ...values] = match; //将数组的第一个值结构出来
const isExact = pathname === url; //判断url和当前路由是否完全匹配
if (exact && !isExact) return null; //如果是严格匹配isExact还未false直接返回null
return {
path, //用于匹配的路径
url: path === "/" && url === "" ? "/" : url, //url匹配的部分
isExact, //是否完全匹配
//keys就是params参数,但是是个Array,为了方便使用将他处理成一个Object
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {})
};
}, null);
}
export default matchPath;
复制代码
看上以上代码以后,我们发现Switch的代码的精髓所在是React.Children.forEach,它非常灵活的去匹配我们应该要渲染的路由,并且在循环内部 做了很多优秀的优化处理
5.3 Redirect
// react-router/modules/Redirect.js
import React from "react";
// createLocation创建location对象,接收的参数只能是string或object,他对此有着不同的处理
// locationsAreEqual用于检测两个location对象是否是一样的
import { createLocation, locationsAreEqual } from "../../history/esm/history";
import Lifecycle from "./Lifecycle.js"; //这是个组件,下面会说到
import RouterContext from "./RouterContext.js"; //依然是使用RouterContext缓存的history、location等属性
import generatePath from "./generatePath.js"; //用来将pathname和params合并到一起的方法
/**
* The public API for navigating programmatically with a component.
*/
/**
* 这里需要注意一下,computedMatch这个属性是被Switch包裹时传递过来的,它也是match对象,我们在这直接用就行了
* to 要重定向的路由
* push 跳转方式
*/
function Redirect({ computedMatch, to, push = false }) {
return (
<RouterContext.Consumer>
{context => {
//依然是从context中取出history,staticContext用不到,是在ssr中使用的
const { history, staticContext } = context;
//push规定Redirect重定向用哪一种跳转方式
const method = push ? history.push : history.replace;
//根据不同的情况去创建location对象
const location = createLocation(
computedMatch
? typeof to === "string"
? generatePath(to, computedMatch.params)
: {
...to,
pathname: generatePath(to.pathname, computedMatch.params)
}
: to
);
// 在静态上下文中进行渲染时,立即设置新位置。
if (staticContext) {
method(location);
return null;
}
return (
<Lifecycle
//onMount其实是componentDidMount生命周期
onMount={() => {
method(location); //跳转
}}
//onUpdate其实是componentDidUpdate生命周期
onUpdate={(self, prevProps) => {
/*
* 接收到新的props参数时,依然通过新的to创建location对象,然后去比较新的location和旧的location
* 是否一样,如果不一样才做跳转
* */
const prevLocation = createLocation(prevProps.to);
if (
!locationsAreEqual(prevLocation, {
...location,
key: prevLocation.key
})
) {
method(location);
}
}}
to={to} //这个传过去没有用到
/>
);
}}
</RouterContext.Consumer>
);
}
export default Redirect;
// react-router/modules/Lifecycle.js
import React from "react";
/**
* 这只是一个很不同的class组件,它返回null,react-router合理的利用的class的生命周期
*/
class Lifecycle extends React.Component {
componentDidMount() {
if (this.props.onMount) this.props.onMount.call(this, this);
}
componentDidUpdate(prevProps) {
if (this.props.onUpdate) this.props.onUpdate.call(this, this, prevProps);
}
componentWillUnmount() {
if (this.props.onUnmount) this.props.onUnmount.call(this, this);
}
render() {
return null;
}
}
export default Lifecycle;
复制代码
5.4 withRouter
// react-router/modules/withRouter.js
import React from "react";
import hoistStatics from "hoist-non-react-statics"; //是一个继承构造函数静态静态属性的库,有兴趣的可以去了解一下
import RouterContext from "./RouterContext.js"; //依然是引入缓存了history、location等对象的RouterContext
/**
* A public higher-order component to access the imperative API
*/
function withRouter(Component) {
const displayName = `withRouter(${Component.displayName || Component.name})`;
const C = (props) => {
const {wrappedComponentRef, ...remainingProps} = props;
return (
<RouterContext.Consumer>
{context => {
return (
<Component
{...remainingProps} //props中的属性原封不动传回去
{...context} //context中有history、location等相关属性
ref={wrappedComponentRef} //防止ref丢失
/>
);
}}
</RouterContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component); //返回C函数,为了防止静态属性丢失,继承Component下的静态属性
}
export default withRouter;
复制代码
withRouter本质是一个高阶组件,它的原理非常简单,就是将history、location等参数混入props中,然后将新的组件返回
6.剖析react-router-config
入口文件
// react-router-config/modules/index.js
export { default as matchRoutes } from "./matchRoutes";
export { default as renderRoutes } from "./renderRoutes";
复制代码
他只有两个api
- renderRoutes 用于渲染我们的路由表,用起来很方便
- matchRoutes 获取当前路由在路由表(不只是路由表)中所匹配的项,用它去屑面包屑十分方便
6.1 renderRoutes
// react-router-config/modules/renderRoutes.js
// renderRoutes做的事很简单,使用Switch包裹,帮我们去渲染Route
import React from "react";
import {Switch, Route} from "../../react-router/[modules](url)"; //引入Switch和Route组件
function renderRoutes(routes, extraProps = {}, switchProps = {}) {
return routes ? (
<Switch {...switchProps}>
{
routes.map((route, i) => {
return <Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props =>
//判断使用哪种方式去渲染
route.render ? (
route.render({...props, ...extraProps, route: route})
) : (
<route.component {...props} {...extraProps} route={route}/>
)
}
/>
}
)}
</Switch>
) : null;
}
export default renderRoutes;
复制代码
6.2 matchRoutes
// react-router-config/modules/matchRoutes.js
// matchRoutes可以去帮我匹配路由表中和当前路由匹配的项
import {matchPath, Router} from "../../react-router/modules";
/**
* routes 路由表
* pathname去查询的路由
* branch默认值
* */
function matchRoutes(routes, pathname, branch = []) {
/***
* 循环路由表去匹配match,几种方式去获取match,如果获取不到则是Router.computeRootMatch(初始值)
*/
routes.some(route => {
const match = route.path
? matchPath(pathname, route)
: branch.length
? branch[branch.length - 1].match // use parent match
: Router.computeRootMatch(pathname); // use default "root" match
if (match) {
//push进去
branch.push({route, match});
//判断如果有下一级就就递归去查询
if (route.routes) {
matchRoutes(route.routes, pathname, branch);
}
}
return match;
});
return branch;
}
export default matchRoutes;
复制代码