「源码解析 」这一次彻底弄懂react-router路由原理 下

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 核心介绍 react-router 路由实现

一 核心api

1 Router-接收location变化,派发更新流

Router 作用是把 history location 等路由信息 传递下去

Router

/* Router 作用是把 history location 等路由信息 传递下去  */
class Router extends React.Component {
   
   
  static computeRootMatch(pathname) {
   
   
    return {
   
    path: '/', url: '/', params: {
   
   }, isExact: pathname === '/' };
  }
  constructor(props) {
   
   
    super(props);
    this.state = {
   
   
      location: props.history.location
    };
    //记录pending位置
    //如果存在任何<Redirect>,则在构造函数中进行更改
    //在初始渲染时。如果有,它们将在
    //在子组件身上激活,我们可能会
    //在安装<Router>之前获取一个新位置。
    this._isMounted = false;
    this._pendingLocation = null;
    /* 此时的history,是history创建的history对象 */
    if (!props.staticContext) {
   
   
      /* 这里判断 componentDidMount 和 history.listen 执行顺序 然后把 location复制 ,防止组件重新渲染 */
      this.unlisten = props.history.listen(location => {
   
   
        /* 创建监听者 */
        if (this._isMounted) {
   
   

          this.setState({
   
    location });
        } else {
   
   
          this._pendingLocation = location;
        }
      });
    }
  }
  componentDidMount() {
   
   
    this._isMounted = true;
    if (this._pendingLocation) {
   
   
      this.setState({
   
    location: this._pendingLocation });
    }
  }
  componentWillUnmount() {
   
   
    /* 解除监听 */
    if (this.unlisten) this.unlisten();
  }
  render() {
   
   
    return (
      /*  这里可以理解 react.createContext 创建一个 context上下文 ,保存router基本信息。children */
      <RouterContext.Provider
          children={
   
   this.props.children || null}
          value={
   
   {
   
   
          history: this.props.history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname),
          staticContext: this.props.staticContext
        }}
      />
    );
  }
}

总结:

初始化绑定listen, 路由变化,通知改变location,改变组件。 react的history路由状态是保存在React.Content上下文之间, 状态更新。

一个项目应该有一个根Router , 来产生切换路由组件之前的更新作用。
如果存在多个Router会造成,会造成切换路由,页面不更新的情况。

2 Switch-匹配正确的唯一的路由

根据router更新流,来渲染当前组件。

/* switch组件 */
class Switch extends React.Component {
   
   
  render() {
   
   
    return (
      <RouterContext.Consumer>
        {
   
   /* 含有 history location 对象的 context */}
        {
   
   context => {
   
   
          invariant(context, 'You should not use <Switch> outside a <Router>');
          const location = this.props.location || context.location;
          let element, match;
          //我们使用React.Children.forEach而不是React.Children.toArray().find()
          //这里是因为toArray向所有子元素添加了键,我们不希望
          //为呈现相同的两个<Route>s触发卸载/重新装载
          //组件位于不同的URL。
          //这里只需然第一个 含有 match === null 的组件
          React.Children.forEach(this.props.children, child => {
   
   
            if (match == null && React.isValidElement(child)) {
   
   
              element = child;
              // 子组件 也就是 获取 Route中的 path 或者 rediect 的 from
              const path = child.props.path || child.props.from;
              match = path
                ? matchPath(location.pathname, {
   
    ...child.props, path })
                : context.match;
            }
          });
          return match
            ? React.cloneElement(element, {
   
    location, computedMatch: match })
            : null;
        }}
      </RouterContext.Consumer>
    );
  }
}

找到与当前path,匹配的组件进行渲染。 通过pathname和组件的path进行匹配。找到符合path的router组件。

matchPath

function matchPath(pathname, options = {
   
   }) {
   
   
  if (typeof options === "string" || Array.isArray(options)) {
   
   
    options = {
   
    path: options };
  }

  const {
   
    path, exact = false, strict = false, sensitive = false } = options;

  const paths = [].concat(path);

  return paths.reduce((matched, path) => {
   
   
    if (!path && path !== "") return null;
    if (matched) return matched;

    const {
   
    regexp, keys } = compilePath(path, {
   
   
      end: exact,
      strict,
      sensitive
    });
    const match = regexp.exec(pathname);
    /* 匹配不成功,返回null */
    if (!match) return null;

    const [url, ...values] = match;
    const isExact = pathname === url;

    if (exact && !isExact) return null;

    return {
   
   
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      params: keys.reduce((memo, key, index) => {
   
   
        memo[key.name] = values[index];
        return memo;
      }, {
   
   })
    };
  }, null);
}

匹配符合的路由。

3 Route-组件页面承载容器

/**
 * The public API for matching a single path and rendering.
 */
class Route extends React.Component {
   
   
  render() {
   
   
    return (
      <RouterContext.Consumer>
        {
   
   context => {
   
   
          /* router / route 会给予警告警告 */
          invariant(context, "You should not use <Route> outside a <Router>");
          // computedMatch 为 经过 swich处理后的 path
          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 };
          let {
   
    children, component, render } = this.props;

          if (Array.isArray(children) && children.length === 0) {
   
   
            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>
    );
  }
}

匹配path,渲染组件。作为路由组件的容器,可以根据将实际的组件渲染出来。通过RouterContext.Consume 取出当前上一级的location,match等信息。作为prop传递给页面组件。使得我们可以在页面组件中的props中获取location ,match等信息。

4 Redirect-没有符合的路由,那么重定向

重定向组件, 如果来路由匹配上,会重定向对应的路由。

function Redirect({
   
    computedMatch, to, push = false }) {
   
   
  return (
    <RouterContext.Consumer>
      {
   
   context => {
   
   
        const {
   
    history, staticContext } = context;
        /* method就是路由跳转方法。 */
        const method = push ? history.push : history.replace;
        /* 找到符合match的location ,格式化location */
        const location = createLocation(
          computedMatch
            ? typeof to === 'string'
              ? generatePath(to, computedMatch.params)
              : {
   
   
                  ...to,
                  pathname: generatePath(to.pathname, computedMatch.params)
                }
            : to
        )
        /* 初始化的时候进行路由跳转,当初始化的时候,mounted执行push方法,当组件更新的时候,如果location不相等。同样会执行history方法重定向 */
        return (
          <Lifecycle
              onMount={
   
   () => {
   
   
              method(location);
            }}
              onUpdate={
   
   (self, prevProps) => {
   
   
              const prevLocation = createLocation(prevProps.to);
              if (
                !locationsAreEqual(prevLocation, {
   
   
                  ...location,
                  key: prevLocation.key
                })
              ) {
   
   
                method(location);
              } 
            }}
              to={
   
   to}
          />
        );
      }}
    </RouterContext.Consumer>
  );
}

初始化的时候进行路由跳转,当初始化的时候,mounted执行push方法,当组件更新的时候,如果location不相等。同样会执行history方法重定向。

二 总结 + 流程分析

总结

history提供了核心api,如监听路由,更改路由的方法,已经保存路由状态state。

react-router提供路由渲染组件,路由唯一性匹配组件,重定向组件等功能组件。

流程分析

当地址栏改变url,组件的更新渲染都经历了什么?😊😊😊
拿history模式做参考。当url改变,首先触发histoy,调用事件监听popstate事件, 触发回调函数handlePopState,触发history下面的setstate方法,产生新的location对象,然后通知Router组件更新location并通过context上下文传递,switch通过传递的更新流,匹配出符合的Route组件渲染,最后有Route组件取出context内容,传递给渲染页面,渲染更新。

当我们调用history.push方法,切换路由,组件的更新渲染又都经历了什么呢?

我们还是拿history模式作为参考,当我们调用history.push方法,首先调用history的push方法,通过history.pushState来改变当前url,接下来触发history下面的setState方法,接下来的步骤就和上面一模一样了,这里就不一一说了。

我们用一幅图来表示各个路由组件之间的关系。

希望读过此篇文章的朋友,能够明白react-router的整个流程,代码逻辑不是很难理解。整个流程我给大家分析了一遍,希望同学们能主动看一波源码,把整个流程搞明白。纸上得来终觉浅,绝知此事要躬行。

写在最后,谢谢大家鼓励与支持🌹🌹🌹,喜欢的可以给笔者点赞关注,公众号:前端Sharing

相关文章
|
9天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
56 22
解析静态代理IP改善游戏体验的原理
|
6天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
61 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
6天前
|
Web App开发 监控 前端开发
React音频播放控制组件开发深度解析
本文介绍了构建React音频控制组件时遇到的关键问题及优化方案。主要包括: 1. **状态同步难题**:解决播放按钮与音频状态不同步的问题,通过双向绑定机制确保一致。 2. **跨浏览器兼容性**:处理Safari和Chrome预加载策略差异,确保`duration`属性正确获取。 3. **进度控制优化**:避免使用`setInterval`,采用`requestAnimationFrame`提升性能;优化拖拽交互,防止音频卡顿。 4. **音量控制进阶**:实现渐变音量调节和静音状态同步。
50 15
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
123 16
|
1月前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
53 0
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
135 2

推荐镜像

更多