react-router原理之路径匹配

首先看一下react-router官网示例

const BasicExample = () => (
  <Router>
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/topics" component={Topics} />
    </div>
  </Router>
);
复制代码

上面代码的运行效果点击这里

本文的目的是讲清楚react-router如何根据浏览器中的url来渲染不同的组件的,至于url是如何改变的(Link组件)请参见下一篇react-router原理之Link跳转

基础依赖path-to-regexp

react-router提供了专门的路由匹配方法matchPath(位于packages/react-router/modules/matchPath.js),该方法背后依赖的其实是path-to-regexp包。

path-to-regexp输入是路径字符串(也就是Route中定义的path的值),输出包含两部分

  • 正则表达式(re)
  • 一个数组(keys)(用于记录param的key信息)

针对path中的参数(下例中的:bar)path-to-regexp在生成正则的时候会把它作为一个捕获组进行定义,同时把参数的名字(bar)记录到数组keys中

var pathToRegexp = require('path-to-regexp')
var keys = []
var re = pathToRegexp('/foo/:bar', keys)
console.log(re);
console.log(keys);

// 输出
/^\/foo\/([^\/]+?)(?:\/)?$/i
[ { name: 'bar',
    prefix: '/',
    delimiter: '/',
    optional: false,
    repeat: false,
    partial: false,
    pattern: '[^\\/]+?' } ]
复制代码

matchPath核心

matchPath方法首先通过path-to-regexp的方法来获取Route上定义的path对应的正则,再将生成的正则表达式与url中的pathname做正则匹配判断是否匹配。

console.log(re.exec('/foo/randal'));   
console.log(re.exec('/foos/randal'));

// 输出
[ '/foo/randal', 'randal', index: 0, input: '/foo/randal' ]
null
复制代码

由于path-to-regexp创建的正则中对param部分创建了捕获组,同时把param的key记录在了单独的数组keys中,因此通过遍历正则匹配的结果和keys数组即可将param的key和value进行关联,如下所示:

const match = re.exec('/foo/randal');
const [url, ...values] = match;

const params = keys.reduce((memo, key, index) => {
  memo[key.name] = values[index];
  return memo;
}, {})

console.log(params) // {"bar": "randal"}
复制代码

最终matchPath针对未匹配的返回null,匹配成功的则返回一个object

return {
    path,    //  /foo/:bar
    url:     //  /foo/randal
    isExact, //  false
    params:  //  {"bar": "randal"}
  };
复制代码

Route渲染

Route组件维护一个state(match),match的值来自于matchPath的执行结果,如下所示

state = {
    match: this.computeMatch(this.props, this.context.router)
  };
  computeMatch({ computedMatch, location, path, strict, exact, sensitive }, router) {
  	 if (computedMatch) return computedMatch; // computedMatch留给Switch使用
    const { route } = router;
    const pathname = (location || route.location).pathname;

    return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
  }
复制代码

当state.match不为null的时候Route才会创建关联的component。

Route关联component有多种形式(render、component、children) children定义形式与render和component的不同在于,children的执行与match无关,即使match为null,children函数也是会执行的,至于为什么会有children这样的设计呢,在接下来的一篇关于Link组件的文章中会提到。

render() {
    const { match } = this.state;
    const { children, component, render } = this.props;
    const props = { match, ...};

    if (component) return match ? React.createElement(component, props) : null;

    if (render) return match ? render(props) : null;

    if (typeof children === "function") return children(props);

    return null;
  }
复制代码

至此关于react-router如何根据url渲染不同Route的组件都讲解完了,不过有时候只用Route的话还是会产生问题,比如:

<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
复制代码

如果当前访问的url是/about的话,上面的写法会在页面上渲染About、User、NoMatch三个组件,其实我们希望的是只渲染About组件。

Switch 路径匹配前置

针对上面的问题,可以用Switch组件包裹一下

<Switch>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={NoMatch}/>
</Switch>
复制代码

经过Switch包裹后, 如果访问url是/about的话则只会渲染About组件了,如果url是/abouts的话,则只会渲染User组件。

Switch组件的特点是只会从子children里挑选一个Route渲染,为了实现只渲染一个的目的,Switch采用的是Route路径匹配前置,不依赖Route的render方法来渲染组件,而是在Switch中就开始Route的路径匹配,一旦发现一个匹配的路径,则将其挑选出来进行渲染。Switch的关键代码如下

render() {
    const { route } = this.context.router;
    const { children } = this.props;
    const location = this.props.location || route.location;

    let match, child;
    // 子children相当于只是选项,Switch负责从中挑选与当前url匹配的Route,被选中的子Route才会触发render方法
    React.Children.forEach(children, element => {
      if (match == null && React.isValidElement(element)) {
        const {
          path: pathProp,
          exact,
          strict,
          sensitive,
          from
        } = element.props;
        const path = pathProp || from;

        child = element;
        match = matchPath(
          location.pathname,
          { path, exact, strict, sensitive },
          route.match
        );
      }
    });

    return match
      ? React.cloneElement(child, { location, computedMatch: match })
      : null;
  }
复制代码

上面代码把matchPath的执行结果match以computedMatch为key传入到Route中了,这样就避免了重复匹配,Route的computeMatch方法就可以直接复用了,computeMatch代码参见前面的Route渲染章节。

进入下一篇react-router原理之Link跳转

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Router 是一个用于在 React 应用中实现路由功能的库。它提供了一种声明式的方式来定义和管理应用程序的路由,使得页面之间的导航和状态管理更加方便和灵活。React Router 的原理可以概括为以下几个关键概念和步骤: 1. **路由器(Router):** React Router 提供了多种类型的路由器组件,如 `BrowserRouter`、`HashRouter` 等。路由器组件负责监听 URL 的变化,并将相应的路由信息传递给应用程序。 2. **路由规则(Route):** 使用 `Route` 组件来定义路由规则。每个 `Route` 组件负责匹配 URL,并在匹配成功时渲染对应的组件。可以通过 `path` 属性来指定匹配路径,通过 `component` 属性来指定要渲染的组件。 3. **导航(Navigation):** React Router 提供了多种导航组件来实现页面之间的跳转,如 `Link`、`NavLink` 等。这些导航组件会生成对应的 `<a>` 标签,并处理点击事件来触发路由的变化。 4. **路由参数(Route Parameters):** 可以通过在路由规则中使用冒号(`:`)来定义动态的路由参数,如 `/users/:id`。在匹配成功后,可以通过 `props.match.params` 来获取路由参数的值。 5. **嵌套路由(Nested Routes):** React Router 支持嵌套路由,即在一个组件内部定义子组件的路由规则。可以通过嵌套的 `Route` 组件来实现。 6. **路由守卫(Route Guards):** React Router 提供了一些钩子函数,如 `beforeEnter`、`beforeLeave` 等,用于实现路由守卫功能。可以在路由跳转前或跳转后执行一些逻辑操作,例如验证用户权限、处理登录状态等。 总的来说,React Router 的原理是通过路由器监听 URL 的变化,根据定义的路由规则匹配对应的组件进行渲染,同时提供导航组件来实现页面之间的跳转。这样可以实现单页面应用(SPA)的路由功能,使得页面的切换和状态管理更加灵活和可控。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值