首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >React源码学习入门(十一)React组件更新流程详解

React源码学习入门(十一)React组件更新流程详解

作者头像
孟健
发布于 2022-12-19 09:09:52
发布于 2022-12-19 09:09:52
84300
代码可运行
举报
文章被收录于专栏:前端工程前端工程
运行总次数:0
代码可运行

React组件更新流程详解

❝本文基于React v15.6.2版本介绍,原因请参见新手如何学习React源码

源码分析

上一篇文章提到最后更新组件是走到了performUpdateIfNecessary方法,让我们来看一看它的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context,
      );
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context,
      );
    } else {
      this._updateBatchNumber = null;
    }
  },

这个方法其实最终走到的是updateComponent方法,并且注意的是,在我们更新state的当前这个组件,它传入的prev和next都是相同的,这个后面会决定willReceiveProps会不会触发。

接下来就是React组件核心更新方法updateComponent,源码位于src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  updateComponent: function(
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext,
  ) {
    var inst = this._instance;

    var willReceive = false;
    var nextContext;

    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    var prevProps = prevParentElement.props;
    var nextProps = nextParentElement.props;

    if (prevParentElement !== nextParentElement) {
      willReceive = true;
    }

    // 1. 如果是receive的情况,触发componentWillReceiveProps
    if (willReceive && inst.componentWillReceiveProps) {
      inst.componentWillReceiveProps(nextProps, nextContext);
    }

    // 2. 合并当前的未处理的state
    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;

    // 3. 计算shouldUpdate
    if (!this._pendingForceUpdate) {
      if (inst.shouldComponentUpdate) {
          shouldUpdate = inst.shouldComponentUpdate(
            nextProps,
            nextState,
            nextContext,
          );
      } else {
        if (this._compositeType === CompositeTypes.PureClass) {
          shouldUpdate =
            !shallowEqual(prevProps, nextProps) ||
            !shallowEqual(inst.state, nextState);
        }
      }
    }

    this._updateBatchNumber = null;
    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext,
      );
    } else {
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
  },

这个函数核心做了3件事情:

  1. 触发componentWillReceiveProps钩子,这个钩子的触发条件是当context或element发生变化时,显然,刚刚我们进来时发现这里的prev和next都是一样的,也就是触发setState的那个组件是不会调用componentWillReceiveProps的。
  2. 合并当前的未处理的state,这个就是将之前setState插入队列里的state一次性合并到当前的state上,这里的合并用的是Object.assign
  3. 计算shouldUpdate,shouldUpdate默认为true,这也是React最大程度保证了组件都能被更新到,我们可以在组件里面实现自己的shouldComponentUpdate方法来决定是否重新render,另外对于PureComponent来说,这里通过shallowEqual来判断state和props是否发生了变化,主要利用的是Object.is判断是否相等。

当组件被判定为shouldUpdate的时候,就会走到_performComponentUpdate来执行更新:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  _performComponentUpdate: function(
    nextElement,
    nextProps,
    nextState,
    nextContext,
    transaction,
    unmaskedContext,
  ) {
    var inst = this._instance;

    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
      prevProps = inst.props;
      prevState = inst.state;
      prevContext = inst.context;
    }

    // 1. 触发willUpdate钩子
    if (inst.componentWillUpdate) {
        inst.componentWillUpdate(nextProps, nextState, nextContext);
    }

    // 2. 更新当前的props和state
    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    // 3. 更新子组件
    this._updateRenderedComponent(transaction, unmaskedContext);

    // 4. componentDidUpdate入队
    if (hasComponentDidUpdate) {
        transaction
          .getReactMountReady()
          .enqueue(
            inst.componentDidUpdate.bind(
              inst,
              prevProps,
              prevState,
              prevContext,
            ),
            inst,
          );
      }
  },

这个函数主要做了以下几件事:

  1. 触发componentWillUpdate钩子
  2. 更新当前组件实例的props和state
  3. 更新子组件
  4. componentDidUpdate入队,这个和componentDidMount是一样的,都是通过Reconciler的transaction在close阶段按照队列触发。

接下来着重看一下更新子组件的流程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  _updateRenderedComponent: function(transaction, context) {
    var prevComponentInstance = this._renderedComponent;
    var prevRenderedElement = prevComponentInstance._currentElement;
    var nextRenderedElement = this._renderValidatedComponent();

    // shouldUpdateReactComponent,则调用receiveComponent更新子组件
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      ReactReconciler.receiveComponent(
        prevComponentInstance,
        nextRenderedElement,
        transaction,
        this._processChildContext(context),
      );
    } else {
      // 否则,卸载当前的组件重新执行mount流程
      var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
      ReactReconciler.unmountComponent(prevComponentInstance, false);

      var nodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedNodeType = nodeType;
      var child = this._instantiateReactComponent(
        nextRenderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
      );
      this._renderedComponent = child;

      var nextMarkup = ReactReconciler.mountComponent(
        child,
        transaction,
        this._hostParent,
        this._hostContainerInfo,
        this._processChildContext(context),
      );

      this._replaceNodeWithMarkup(
        oldHostNode,
        nextMarkup,
        prevComponentInstance,
      );
    }
  },

这个函数核心是判断shouldUpdateReactComponent,如果是的话,那就走子组件的更新流程,否则,就销毁子组件,重新挂载。

一般来说,针对子组件的销毁和重建是比较消耗性能的,而且会使得生命周期函数被重复触发,所以React采用一个简单的原则来判断是否需要重新挂载,这也是Diff算法的起点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function shouldUpdateReactComponent(prevElement, nextElement) {
  var prevEmpty = prevElement === null || prevElement === false;
  var nextEmpty = nextElement === null || nextElement === false;
  if (prevEmpty || nextEmpty) {
    return prevEmpty === nextEmpty;
  }

  var prevType = typeof prevElement;
  var nextType = typeof nextElement;
  if (prevType === 'string' || prevType === 'number') {
    return nextType === 'string' || nextType === 'number';
  } else {
    return (
      nextType === 'object' &&
      prevElement.type === nextElement.type &&
      prevElement.key === nextElement.key
    );
  }
}

解读一下这个关键函数,分几类情况:

  1. emptyComponent的场景,如果同为false或者同为null,则不需要重新挂载,否则重新挂载。
  2. stringnumber的场景,也就是一个文本节点,前后都是文本节点的话,是不需要重新挂载的。
  3. 其他的情况,得看两个组件是否是同一个类型,以及key是否相同,若两个条件同时满足,则不需要重新挂载。

所有触发的子组件,默认按照receiveComponent的模式往下递归,如果遇到React组件,又会重复之前的步骤,它的入口是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  receiveComponent: function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;

    this._pendingElement = null;

    this.updateComponent(
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext,
    );
  },

updateComponent流程上面已经分析过了,不再赘述。

小结一下

本文主要分析了React组件的更新过程,重在几个生命周期函数的触发,以及更新策略,具体真正的更新是在DOMComponent中。我们可以简单总结一下React组件更新的流程图:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟健的前端认知 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一文学会curl和curl详解
cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载工具。
星哥玩云
2022/05/28
5.3K0
一文学会curl和curl详解
还在用 postman?手把手教你用 curl 提高工作效率
curl 是 Linux 系统上一款网络工具,它的首字母 c 代表的是 client,表示它是客户端程序。通过 URL 方式,可以实现客户端与服务器之间传递数据。
用户3105362
2021/11/12
1.4K0
Linux - curl 命令
后面的栗子,基本都会加 -v,是为了看请求的详细过程,更容易看到对应的参数已生效,实际使用不需要每次都 -v
小菠萝测试笔记
2021/06/17
9K0
Linux - curl 命令
linux使用curl命令_如何使用curl从Linux命令行下载文件
The Linux curl command can do a whole lot more than download files. Find out what curl is capable of, and when you should use it instead of wget.
用户7886150
2020/12/30
5.3K0
Linux curl 常用示例
如果想了解curl选项的详细说明,请参考前一篇文章「Linux curl 命令详解」。
踏歌行
2020/10/15
10.1K0
Linux curl 常用示例
关于curl网站运维与开发的那些事
curl网站开发指南 常见参数: -A/--user-agent <string> 设置用户代理发送给服务器 -b/--cookie <name=string/file> cookie字符串或文件读取位置 -c/--cookie-jar <file> 操作结束后把cookie写入到这个文件中 -C/--continue-at <offset> 断点续转 -D/--dump-header <file>
学到老
2018/03/19
1.1K0
Linux系列之学会使用CURL命令进行URL测试
curl命令是一个利用URL规则在shell终端命令行下工作的文件传输工具;curl命令作为一款强力工具,curl支持包括HTTP、HTTPS、ftp等众多协议,还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征;做网页处理流程和数据检索自动化。
SmileNicky
2022/05/07
2.3K0
Linux系列之学会使用CURL命令进行URL测试
curl----命令行请求工具
curl是一个使用URL语法传输数据的命令行工具,支持DICT、FILE、FTP、FTPS、GOPHER、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、POP3、POP3S、RTMP、RTSP、SCP、SFTP、SMTP、SMTPS、TELNET和TFTP。 curl支持SSL证书、HTTP POST、HTTP PUT、FTP上传、基于HTTP表单的上传、代理、cookie、用户+密码身份验证(Basic、Digest、NTLM、Negotiate、kerberos…)、文件传输恢复、代理隧道和其他有用技巧。
cultureSun
2023/05/18
1.1K0
Linux系列之学会使用CURL命令
curl命令是一个利用URL规则在shell终端命令行下工作的文件传输工具;curl命令作为一款强力工具,curl支持包括HTTP、HTTPS、ftp等众多协议,还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征;做网页处理流程和数据检索自动化。
SmileNicky
2020/07/21
1.5K0
Linux curl 命令详解
curl 是一个工具,用于传输来自服务器或者到服务器的数据。「向服务器传输数据或者获取来自服务器的数据」
踏歌行
2020/10/15
39.7K0
Linux curl 命令详解
curl 用法简介
curl 是一个命令行客户端,支持多种传输协议,最经常使用的场景就是在终端请求服务器资源。
后端码匠
2022/01/18
2K0
curl 用法简介
CURL 那些不太为人知但强大的功能
对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》
山河已无恙
2025/03/29
1210
CURL 那些不太为人知但强大的功能
Linux 下命令行CURL的15种常见示例!
在本教程中,我们将介绍Linux中的cURL命令。我们会给出一些示例来指导您了解这个强大的实用程序的功能,帮助您理解它所能实现的所有功能。
用户6543014
2019/12/17
18.4K0
Linux 下命令行CURL的15种常见示例!
curl命令
curl(CommandLine Uniform Resource Locator),是一个利用 URL 语法,在命令行终端下使用的网络请求工具,支持 HTTP、HTTPS、FTP 等协议。curl也有用于程序开发使用的版本 libcurl。
用户10638239
2024/01/16
5990
linux curl 测试域名劫持
-v 参数可以显示一次 http 通信的整个过程,包括端口连接和 http request 头信息。
葫芦
2019/05/24
10.9K0
GitHub Actions 教程:定时发送天气邮件
2019年11月,GitHub 正式开放了 GitHub Actions 这个功能,现在不用申请就能使用。
ruanyf
2020/01/22
2.1K1
curl 命令常用场景
curl 命令向 www.qq.com 发出 GET 请求,服务器返回的内容会在命令行输出。
Yorkyu
2022/03/22
6880
curl 命令常用场景
Linux - curl -w 参数详解
当 curl 没有指定 -L、--location 参数跟随重定向时,此变量将显示重定向实将跳转的实际 URL
小菠萝测试笔记
2021/06/17
5.9K0
Linux - curl -w 参数详解
linux之curl命令
原文链接:https://rumenz.com/rumenbiji/linux-curl.html
入门笔记
2021/10/20
3.2K0
curl的使用
curl是常用发起http请求工具,今天就整理下如何正确的使用curl命令,来提高工作效率。
付威
2020/01/21
1.7K0
相关推荐
一文学会curl和curl详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验