专栏首页黄腾霄的博客2020-5-16-React-Router源码简析

2020-5-16-React-Router源码简析

今天来和大家解析下React-Router的源码。


React-Router是React生态中最重要的组件之一。

他提供了动态的前端路由功能,能让我们在前端应用实现,高效的SPA应用。

那么这个东西是怎么实现的呢?

我们来一起看下它的源码

Router.js

constructor(props) {
    super(props);

    this.state = {
      location: props.history.location
    };

    // This is a bit of a hack. We have to start listening for location
    // changes here in the constructor in case there are any <Redirect>s
    // on the initial render. If there are, they will replace/push when
    // they mount and since cDM fires in children before parents, we may
    // get a new location before the <Router> is mounted.
    this._isMounted = false;
    this._pendingLocation = null;

    if (!props.staticContext) {
      this.unlisten = props.history.listen(location => {
        if (this._isMounted) {
          this.setState({ location });
        } else {
          this._pendingLocation = location;
        }
      });
    }
  }

render() {
    return (
      <RouterContext.Provider
        value=
      >
        <HistoryContext.Provider
          children={this.props.children || null}
          value={this.props.history}
        />
      </RouterContext.Provider>
    );
  }

在Router中我们主要看它的构造函数和render函数。

这里构造函数中将location作为自己的state,并且监听了location的变化。

在render中利用了React的Context提供了RouterContext,HistoryContext两个Context信息,供子元素使用。

值得注意的是match: Router.computeRootMatch(this.state.location.pathname),

这里Router利用了当前location的pathname计算,指向了根地址

Route.js

class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>//获取RouterContext共享的state
        {context => {
          invariant(context, "You should not use <Route> outside a <Router>");
			//计算location,match,并组装至props
          const location = this.props.location || context.location;
          const match = this.props.computedMatch
            ? this.props.computedMatch // <Switch> already computed the match for us
            : this.props.path
              ? matchPath(location.pathname, this.props)
              : context.match;
			
          const props = { ...context, location, match };
			//从this.props解析children,component,render
          let { children, component, render } = this.props;

          // Preact uses an empty array as children by
          // default, so use null if that's the case.
          if (Array.isArray(children) && children.length === ) {
            children = null;
          }
			//渲染逻辑
          return (
            <RouterContext.Provider value={props}>
              {props.match
                ? children
                  ? typeof children === "function"
                    ? __DEV__
                      ? evalChildrenDev(children, props, this.props.path)
                      : children(props)
                    : children
                  : component
                    ? React.createElement(component, props)
                    : render
                      ? render(props)
                      : null
                : typeof children === "function"
                  ? __DEV__
                    ? evalChildrenDev(children, props, this.props.path)
                    : children(props)
                  : null}
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}

Route.js的源码如上,比较长,我在关键处做了注释,我们一步步解析。

首先最外层是RouterContext.Consumer,用于获取父组件定义的RouterContext的状态。

用于辅助计算location,和match,并且封装仅props,在下面的渲染中,又作为RouterContext.Provider 的参数向下传递。

这样的好处主要是实现,嵌套路由,父元素Route处理部分路由,子元素继续处理。

核心渲染

{props.match
  ? children
    ? typeof children === "function"
      ? children(props)
      : children
    : component
      ? React.createElement(component, props)
      : render
        ? render(props)
        : null
  : typeof children === "function"
    ? children(props)
    : null}

上面一段是Route的核心渲染方法,利用了嵌套的三元函数,决定了如何进行组件渲染(已删减调试方法)。

思维导图如下

当props匹配了路由时,先判断是否匹配,如果不匹配就将props向下传递。

如果匹配了,先判断是否存在children,如果存在优先选择children。

否则再判断是否存在component,如果是,就调用React的createElement,创建React组件

否则,如果有render,则调用render方法。

源码解析

我们可以从上述的源码中看到:

  • Route的component,render,children三个属性是互斥的
  • 优先级children>component>render
  • children在无论路由匹配与否,都会渲染

这一点也可以在React-Router的官网中得到相应的信息

小结

通过分析源码我们了解到了

  • React-Router通过监听location变化触发刷新,实现路由更新
  • 利用React的Context机制,实现嵌套路由分析,和状态传递
  • Route组件中component,render,children三个属性的渲染机制
  • 所有的机制都在render中,所以能够在渲染时进行动态路由

参考文档:


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/React-Router%E6%BA%90%E7%A0%81%E7%AE%80%E6%9E%90.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2019-1-25-wcf入门(4)

    创建单向模式的操作很简单,只要在OperationContract中将IsOneWay设置成True即可

    黄腾霄
  • 2019-12-1-微信小程序视频流静音后画面卡死问题研究

    在静音期间试着使用ffplay进行播放,流能够正常播放,所以不存在推流端视频流丢帧的问题

    黄腾霄
  • 为什么同样的WPF控件在不同的电脑上呈现外观不一致

    今天有同事跑过来说遇到了一个奇怪的bug,同样的程序在win7和win10上界面相差了2个像素

    黄腾霄
  • Flutter ListView 拖拽排序了解一下

    前面我们对于 ListView 的操作讲过 Flutter 滑动删除最佳实践,那现在我们来了解一下 ListView 的拖拽排序。

    Flutter笔记
  • 编程小白 | 每日一练(196)

    这道理放在编程上也一并受用。在编程方面有着天赋异禀的人毕竟是少数,我们大多数人想要从编程小白进阶到高手,需要经历的是日积月累的学习,那么如何学习呢?当然是每天都...

    闫小林
  • [缘分]霸面,四小时,百度算法offer

    [待更新] 秋招快结束了,本想写点多家公司的面经记录一下,但是大都记不清了,只有百度的面试过程还记的清楚,希望能够帮助到今年的同学或者以后的学弟学妹。总的来说,...

    牛客网
  • error: (-215:Assertion failed) !ssize.empty() in function ‘cv::resize‘

    可能错误: 1.图片路径写成了如下形式:C:\Users\Desktop\test\ 正确的应该为:C:/Users/Desktop/test/ (在程序...

    于小勇
  • 腾讯 AngelFL 联邦学习平台揭秘

    ? 作者:AI前线 数据里蕴含着价值。在人工智能时代,机器学习尤其深度学习模型的获得需要大量的训练数据作为前提。但是在很多业务场景中,模型的训练数据往往分散...

    腾讯技术工程官方号
  • iOS进度指示器——NSProgress 原

            在iOS7之前,系统一直没有提供一个完整的框架来描述任务进度相关的功能。这使得在开发中进行耗时任务进度的监听将什么麻烦,在iOS7之后,系统提供...

    珲少
  • 经典Bug永流传---每周一“虫”(二十八)

    步骤:在XX页面,点击某款游戏进入以后,点击评论,然后点击我要评论,选中5颗星,然后输入内容,满一屏后,评论的星星会往上移动,

    厦门-安仔

扫码关注云+社区

领取腾讯云代金券