Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
React 源码深度解读(九):单个元素更新
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。
Dickensl
2022/06/14
7680
React 源码深度解读(九):单个元素更新
深入理解React Native页面构建渲染原理
前言 React Native 是最近非常火的一个话题,因为它的语法简介,跨平台等特性,赢得了各大平台的青睐,虽然前期是有一些坑。 基本概念解释 React 是一套可以用简洁的语法高效绘制 DOM 的框架,所谓的“高效”,是因为 React 独创了 Virtual DOM 机制。Virtual DOM 是一个存在于内存中的 JavaScript 对象,它与 DOM 是一一对应的关系,也就是说只要有 Virtual DOM,我们就能渲染出 DOM。当界面发生变化时,得益于高效的 DOM Diff 算法,我们能
xiangzhihong
2018/01/26
1.8K0
带你实现react源码的核心功能
React 的代码还是非常复杂的,虽然这里是一个简化版本。但是还是需要有不错的面向对象思维的。React 的核心主要有一下几点。
goClient1992
2022/10/03
1.3K0
React源码学习入门(十二)DOM组件更新流程与Diff算法
前面提到过最终的更新还是要在DOMComponent完成,而setState后,触发到DOM的更新入口是receiveComponent,源码在src/renderers/dom/shared/ReactDOMComponent.js:
孟健
2022/12/19
7260
React源码学习入门(十二)DOM组件更新流程与Diff算法
谈谈React中Diff算法的策略及实现
1、什么是Diff算法 传统Diff:diff算法即差异查找算法;对于Html DOM结构即为tree的差异查找算法;而对于计算两颗树的差异时间复杂度为O(n^3),显然成本太高,React不可能采用这种传统算法; React Diff: 之前说过,React采用虚拟DOM技术实现对真实DOM的映射,即React Diff算法的差异查找实质是对两个JavaScript对象的差异查找; 基于三个策略: Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。(tree diff) 拥有相同
keyWords
2019/03/13
1.3K0
谈谈React中Diff算法的策略及实现
React 源码深度解读(十):Diff 算法详解
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深。阅读 React 源码是一个非常艰辛的过程,在学习过程中给我帮助最大的就是这个系列文章。作者对代码的调用关系梳理得非常清楚,而且还有配图帮助理解,非常值得一读。站在巨人的肩膀之上,我尝试再加入自己的理解,希望对有志于学习 React 源码的读者带来一点启发。
Dickensl
2022/06/14
1.3K0
React 源码深度解读(十):Diff 算法详解
React源码学习入门(八)React组件挂载Component细节流程
在上一篇文章的最后,我们走到了mountComponentIntoNode,它通过调用ReactReconciler.mountComponent来获取Markup,这个也是React执行挂载的核心入口,源码位于src/renderers/shared/stack/reconciler/ReactReconciler.js:
孟健
2022/12/19
6670
React源码学习入门(八)React组件挂载Component细节流程
从recat源码角度看setState流程
setState() 将对组件 state 的更改排入队列批量推迟更新,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。其实setState实际上不是异步,只是代码执行顺序不同,有了异步的感觉。
flyzz177
2022/09/27
5810
[第10期] 了解 React setState 运行机制
使用React 的时候, 难免要用到setState , 有一些基础还是需要了解一下。
皮小蛋
2020/03/02
1.3K0
从源码层次了解 React 生命周期:更新
今天我们继续从源码层面看 React 的更新阶段,是如何触发类函数的生命周期函数的。
前端西瓜哥
2022/12/21
6390
从源码层次了解 React 生命周期:更新
React中的setState的同步异步与合并(2)
this.setState会通过引发一次组件的更新过程来引发重新绘制。也就是说setState的调用会引起React的更新生命周期的四个函数的依次调用:
Qwe7
2022/06/09
8350
React State(状态): React通过this.state来访问state,通过this.setState()方法来更新stateReact State(状态)
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。 React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。 以下实例中创建了 LikeButton 组件,getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。
一个会写诗的程序员
2018/08/17
2.3K0
React源码分析与实现(二):状态、属性更新 -> setState
setState的源码比较简单,而在执行更新的过程比较复杂。我们直接跟着源码一点一点屡清楚。
Nealyang
2019/09/29
1.5K0
React源码分析与实现(二):状态、属性更新 -> setState
react面试题
调用setState之后发生了什么? 在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。 扩展1: setState的第一个参数除了对象,还能传什么? ---函数,参数为当前state
用户7162790
2022/03/23
7980
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深。阅读 React 源码是一个非常艰辛的过程,在学习过程中给我帮助最大的就是这个系列文章。作者对代码的调用关系梳理得非常清楚,而且还有配图帮助理解,非常值得一读。站在巨人的肩膀之上,我尝试再加入自己的理解,希望对有志于学习 React 源码的读者带来一点启发。
Dickensl
2022/06/14
4370
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
从componentWillReceiveProps说起
componentWillReceiveProps通常被认为是propsWillChange,我们确实也通过它来判断props change。但实际上,componentWillReceiveProps在每次rerender时都会调用,无论props变了没:
ayqy贾杰
2019/06/12
2.5K0
React Native之组件Component与PureComponent
众所周知,React Native的页面元素是由一个一个的组件所构成的,这些组件包括系统已经提供的组件,如View、TextInput等,还有一些第三方库提供的组件,以及自定义的组件。通常在封装组件的时候都会继承Component,不过在React 15.3版本中系统提供了PureComponent,下面就来看一下这两个组件的区别。
xiangzhihong
2022/11/30
3470
​我用300行代码实现了React
我们先使用最新版create-react-app,在example/目录下创建一个demo项目:
孟健
2022/12/19
9120
​我用300行代码实现了React
React入门系列(四)组件的生命周期
React的核心是组件,组件在创建和渲染的过程中,需要调用固定的钩子函数,也称为组件的“生命周期”。利用生命周期函数,可以做初始化工作,并在渲染过程中实现一些特定功能。
娜姐
2021/01/14
8950
React入门系列(四)组件的生命周期
React移动web极致优化
本文start kit: steamer-react PS: 要看效果得将一个QQ群组转换成家校群,可到此网址进行转换(手Q/PC都可以访问): http://qun.qq.com/homework
李成熙heyli
2018/01/05
1.6K0
React移动web极致优化
相关推荐
React 源码深度解读(九):单个元素更新
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验