深入理解react-router路由系统

1.什么是路由?

在web应用开发中,路由是非常重要的一部分,在浏览器当前的URL发生变化时,路由系统会做出一些响应,用来保证用户界面与URL同步。随着单页面 应用的到来,为之服务的第三方库也相继出现了,现在的主流框架也都有自己的路由,比如ReactVueAngular等,那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

01 (2).png

其实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;


复制代码

最终效果

02.gif

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中有historylocation等相关属性
                            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;
复制代码

猜你喜欢

转载自juejin.im/post/7016907223724982308