从recat源码角度看setState流程

news2024/12/29 11:35:42

setState

setState() 将对组件 state 的更改排入队列批量推迟更新,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。其实setState实际上不是异步,只是代码执行顺序不同,有了异步的感觉。

使用方法 setState(stateChange | updater [, callback])

  • stateChange - 作为被传入的对象,将被浅层合并到新的 state 中
  • updater - (state, props) => stateChange,返回基于 state 和 props 构建的新对象,将被浅层合并到新的 state 中
  • callback - 为可选的回调函数

使用 setState() 改变状态之后,立刻通过this.state拿不到最新的状态

可以使用 componentDidUpdate() 或者 setState(updater, callback) 中的回调函数 callback 保证在应用更新后触发,通常建议使用 componentDidUpdate()

多次setState()函数调用产生的效果会合并

为了更好的感知性能,React 会在同一周期内会对多个 setState() 进行批处理。通过触发一次组件的更新来引发回流。后调用的 setState() 将覆盖同一周期内先调用 setState() 的值。所以如果是下一个 state 依赖前一个 state 的话,推荐给 setState() 传 function

onClick = () => {
    this.setState({ quantity: this.state.quantity + 1 });
    this.setState({ quantity: this.state.quantity + 1 });
}
// react中,这个方法最终会变成
Object.assign(
    previousState,
    {quantity: state.quantity + 1},
    {quantity: state.quantity + 1},
    ...
)

同步 | 异步更新

  • 同步更新
    • React 引发的事件处理(比如通过onClick引发的事件处理)
    • React 生命周期函数
  • 异步更新
    • 绕过React通过 addEventListener 直接添加的事件处理函数
    • 通过 setTimeout || setInterval 产生的异步调用

setState()被调用之后,源码执行栈

react 参照版本 15.6.0

1. setState()

源码路径 src/isomorphic/modern/class/ReactBaseClasses.js

React组件继承自React.Component,而setState是React.Component的方法,因此对于组件来讲setState属于其原型方法

ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
}

2. enqueueSetState(); enqueueCallback()

源码路径 src/renderers/shared/stack/reconciler/ReactUpdateQueue.js

这个文件导出了一个 ReactUpdateQueue 对象(React更新队列)

enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',
    );
    if (!internalInstance) {
      return;
    }
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    enqueueUpdate(internalInstance);
}
enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
    if (!internalInstance) {
      return null;
    }
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  }

3. enqueueUpdate()

源码路径 src/renderers/shared/stack/reconciler/ReactUpdates.js

如果处于批量更新模式,也就是 isBatchingUpdates 为 true 时,不进行state的更新操作,而是将需要更新的 component 添加到 dirtyComponents 数组中。

如果不处于批量更新模式,对所有队列中的更新执行 batchedUpdates 方法。

function enqueueUpdate(component) {
  ensureInjected();
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

4. batchedUpdates()

源码路径 src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

如果 isBatchingUpdates 为 true,当前正处于更新事务状态中,则将 Component 存入 dirtyComponent 中, 否则调用 batchedUpdates 处理,发起一个 transaction.perform()。

var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

5. transaction initialize and close

源码路径 src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

Transaction 中注册了两个 wrapper,RESET_BATCHED_UPDATESFLUSH_BATCHED_UPDATES

initialize 阶段,两个 wrapper 都是空方法,什么都不做。

close 阶段,RESET_BATCHED_UPDATES 将 isBatchingUpdates 设置为false;FLUSH_BATCHED_UPDATES 运行 flushBatchedUpdates 执行update。

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

6. 渲染更新

ReactUpdates.flushBatchedUpdates() - ReactUpdates.runBatchedUpdates() - ReactCompositeComponent.performUpdateIfNecessary() - receiveComponent() + updateComponent()

  1. runBatchedUpdates循环遍历dirtyComponents数组,主要干两件事:

    • 首先执行 performUpdateIfNecessary 来刷新组件的 view
    • 执行之前阻塞的 callback
  2. receiveComponent 最后会调用 updateComponent

  3. updateComponent 中会执行 React 组件存在期的生命周期方法,如 componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 从而完成组件更新的整套流程

  4. 在shouldComponentUpdate之前,执行了_processPendingState方法,该方法主要对state进行处理:

    • 如果更新队列为null,那么返回原来的state;
    • 如果更新队列有一个更新,那么返回更新值;
    • 如果更新队列有多个更新,那么通过for循环将它们合并;
  5. 在一个生命周期内,在componentShouldUpdate执行之前,所有的state变化都会被合并,最后统一处理。

flushBatchedUpdates(); runBatchedUpdates() 源码路径 src/renderers/shared/stack/reconciler/ReactUpdates.js

var flushBatchedUpdates = function() {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};
function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  dirtyComponents.sort(mountOrderComparator);
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    // dirtyComponents中取出一个component
    var component = dirtyComponents[i];
    // 取出dirtyComponent中的未执行的callback
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      if (component._currentElement.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }
    // 执行updateComponent
    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber,
    );
    // 执行dirtyComponent中之前未执行的callback
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance(),
        );
      }
    }
  }
}

相关参考视频讲解:进入学习

performUpdateIfNecessary() 源码路径 src/renderers/shared/stack/reconciler/ReactReconciler.js

performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber,
  ) {
    internalInstance.performUpdateIfNecessary(transaction);
  },
};

performUpdateIfNecessary() 源码路径 src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

  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() 源码路径 src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

事务概念

简单地说,一个 Transaction 就是将需要执行的 anyMethod 使用 wrapper 封装起来,在通过 Transaction 提供的 perform 方法执行,而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,perform 之后再执行所有 wrapper 中的 close 方法。一组 initialize 及 close 方法称为一个 wrapper,Transaction 支持多个 wrapper 叠加。

React 中的 Transaction 提供了一个 Mixin 方便其他模块实现自己需要的事务。要实现自己的事务,需要额外实现一个抽象的 getTransactionWrappers() 接口,这个接口是 Transaction 用来获取所有 wrapper 的 initialize 和 close 方法,因此需要返回一个数组对象,每个对象分别有 key 为 initialize 和 close 的方法。

var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});

setState 流程

setState 流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件,React大量运用了注入机制,这样每次注入的都是同一个实例化对象,防止多次实例化

  1. enqueueSetState 将 state 放入队列中,并调用 enqueueUpdate 处理要更新的 Component
  2. 如果组件当前正处于 update 事务中,则先将 Component 存入 dirtyComponent 中。否则调用 batchedUpdates 处理
  3. batchedUpdates 发起一次 transaction.perform() 事务
  4. 开始执行事务初始化,运行,结束三个阶段
    • 初始化:事务初始化阶段没有注册方法,故无方法要执行
    • 运行:执行 setSate 时传入的 callback 方法,一般不会传 callback 参数
    • 结束:执行 RESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATES 这两个 wrapper 中的 close 方法
  5. FLUSH_BATCHED_UPDATES 在 close 阶段,flushBatchedUpdates 方法会循环遍历所有的 dirtyComponents ,调用 updateComponent 刷新组件,并执行它的 pendingCallbacks , 也就是 setState 中设置的 callback

组件挂载后,setState一般是通过DOM交互事件触发,如 click

  1. 点击button按钮
  2. ReactEventListener 会触发 dispatchEvent方法
  3. dispatchEvent 调用 ReactUpdates.batchedUpdates
  4. 进入事务,init 为空, anyMethod 为 ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    • handleTopLevelImpl 是在这边调用DOM事件对应的回调方法
    • 然后是setState()
      • 将state的变化和对应的回调函数放置到 _pendingStateQueue ,和 _pendingCallback 中
      • 把需要更新的组件放到 dirtyComponents 序列中
    • 执行 perform()
    • 执行 close 渲染更新
dispatchEvent: function(topLevelType, nativeEvent) {
    if (!ReactEventListener._enabled) {
      return;
    }

    var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
      topLevelType,
      nativeEvent,
    );
    try {
      // Event queue being processed in the same cycle allows
      // `preventDefault`.
      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    } finally {
      TopLevelCallbackBookKeeping.release(bookKeeping);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

助力生产质量检验,基于YOLOV5实现香烟质量缺陷检测

生产质量环境的检验始终是一个热门的应用场景&#xff0c;在之前一些项目和文章中我也做过一些相关的事情&#xff0c;比如PCB电路板相关的&#xff0c;如下&#xff1a;《助力质量生产&#xff0c;基于目标检测模型MobileNetV2-YOLOv3-Lite实现PCB电路板缺陷检测》本质的目的就…

含泪赔了近200万,我终于明白不是什么人都能干电商的……

文|螳螂观察 作者|图霖 又是一年年货节&#xff0c;围绕电商相关话题的讨论正在增多。 都说现在入行做电商十有九亏&#xff0c;但《螳螂观察》注意到一组数据&#xff1a;截至7月31日&#xff0c;过去一年入淘创业者的数量仍在增长&#xff0c;淘宝天猫净增了近120万商家&a…

每天五分钟机器学习:如何使用误差分析来构造最优的异常检测算法

本文重点 在异常检测算法中,我们要做的事情之一就是使用正态(高斯)分布来对特征向量进行建模p(xi;μi,σi),所以输入到算法中的特征变量很重要。 特征变量不符合高斯分布怎么办 首先我们需要知道一点,有些特征变量的数据并不符合高斯分布,但是我们假设它们符合高斯分…

中西方哲学史概要

中西方哲学史概要 哲学的定义 哲学在古希腊是 “爱智慧” 的意思&#xff0c;一切的知识都可以称之为“哲学”&#xff0c;它是对基本和普遍之问题研究的学科&#xff0c;是关于世界观的理论体系。很多人说懂哲学的人很可怕&#xff0c;其实这是错误的&#xff0c;因为真正懂哲…

【linux命令】查看进程活动的命令

ps进程信息 ps用于显示系统内的所有进程 -l或l 采用详细的格式来显示进程状况 常用方式&#xff1a; ps -elf 和ps -ef rootecs-x-large-2-linux-20200309113627:/home/etcd_msg_server# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 …

C++入门 -- 模板初阶与string简介

目录 模板&#xff1a; 函数模板 类模板 STL简介&#xff1a; string: string类对象的常见构造 string类对象的容量操作 string类对象的访问及遍历 模板&#xff1a; 在C语言阶段&#xff0c;当我们需要交换两个int类型的数据就需要写一个支持int类型交换的Swap函数…

如何使用ArcGIS Pro自动矢量化建筑

概述相信你在使用ArcGIS Pro的时候已经发现了一个问题&#xff0c;那就是ArcGIS Pro没有ArcScan&#xff0c;确实在ArcGIS Pro中Esri移除了ArcScan&#xff0c;没有了ArcScan我们如何自动矢量化地图&#xff0c;从地图中提取建筑等要素呢&#xff0c;这里为大家介绍另外一种方法…

可视化深度学习模型的方法/工具

介绍 可以使用 TensorBoard 来可视化深度学习模型。TensorBoard 是 TensorFlow 中的一个可视化工具,可以帮助您在训练期间和训练后可视化模型的训练曲线、模型结构、激活值和权值分布等信息。可以使用 TensorBoard 的命令行工具或在 Jupyter 笔记本中使用 TensorBoard magic …

【Linux】权限理解(粘滞位设置)

目  录1 权限的概念2 权限管理2.1 文件类型及其访问权限2.2 文件权限值的表示方法2.3 文件访问权限设置2.4 目录权限&#xff08;粘滞位&#xff09;1 权限的概念 所谓权限&#xff0c;实际上是对人的约束&#xff0c;在Linux中&#xff0c;是对普通用户的约束。一件事情&…

蓝桥杯嵌入式之 Keil 仿真与调试

这篇文章为大家讲解 蓝桥杯嵌入式的 Keil 仿真与调试 &#xff0c; 这在比赛和今后的工作中都是常用的。大家看完后一定会对此有一个深刻的认识。 文章目录前言一、调试器的准备工作&#xff1a;1.在 Keil uVision集成开发环境下&#xff0c;选择CMSIS-DAP Debugger调试器。2.在…

final关键字深入解析

final关键字特性 final关键字在java中使用非常广泛&#xff0c;可以申明成员变量、方法、类、本地变量。一旦将引用声明为final&#xff0c;将无法再改变这个引用。final关键字还能保证内存同步&#xff0c;本博客将会从final关键字的特性到从java内存层面保证同步讲解。这个内…

SpringBoot项目从18.18M瘦身到0.18M

一、前言 SpringBoot部署起来虽然简单&#xff0c;如果服务器部署在公司内网&#xff0c;速度还行&#xff0c;但是如果部署在公网&#xff08;阿里云等云服务器上&#xff09;&#xff0c;部署起来实在头疼&#xff1a;编译出来的 Jar 包很大&#xff0c;如果工程引入了许多开…

GAMES101作业6及课程总结(重点解决SAH扩展作业)

这次作业相对于作业5会麻烦一点点&#xff0c;而且框架相较于作业五的也麻烦了一点&#xff0c;当然作业的难点其实主要还是在扩展作业SAH那块。 目录课程总结与理解&#xff08;光线追踪&#xff09;框架梳理作业一&#xff1a;光线生成作业二&#xff1a;光线-三角形相交作业…

Neo4j图数据库 批量写入与查询

1 前言 1-1 简介 工作中需要对所有的实体数据进行存储构建实体知识图谱&#xff0c;为基于知识图谱的问答提供数据基础。选择使用Neo4j作为数据库进行存储。以下是关于Neo4j的简介。 1-2 任务背景 将处理好的实体数据(共计1100万)写入图数据库中&#xff0c;并且提供查询接口…

量子计算(二十):量子算法简介

文章目录 量子算法简介 一、概述 二、量子经典混合算法 量子算法简介 一、概述 量子算法是在现实的量子计算模型上运行的算法&#xff0c;最常用的模型是计算的量子电路模型。经典&#xff08;或非量子&#xff09;算法是一种有限的指令序列&#xff0c;或一步地解决问题的…

乐视--996、内卷、裁员环境下一朵“奇葩”

在2022.12.28日我们发表了一篇“为什么四天工作制才是企业良药&#xff0c;而非裁员”,大家认为四天工作制与我们的距离就像实现“一个小目标”一样&#xff0c;不太可能。这不他来了&#xff0c;乐视来了&#xff0c;他真的来了&#xff0c;“鸡毛真的上天了”。他来了他来了他…

SQL技巧:使用AVG()函数计算占比

计算方式对比 一般计算占比&#xff0c;比如转换率、留存率等&#xff0c;都是先分组求和再相除得到结果&#xff0c;但是在一定的条件下&#xff0c;可以直接使用AVG()求出百分比。 比如&#xff0c;要求统计报名转化率&#xff0c;报名转化率公式为转化率报名人数/浏览人数…

内核解读之内存管理(8)内存模型

文章目录基本的术语CONFIG_FLATMEM&#xff08;平坦内存模型&#xff09;稀疏的内存模型基本的术语 在介绍内存模型之前需要了解一些基本的知识。 1、什么是page frame&#xff1f; 在linux操作系统中&#xff0c;物理内存被分成一页页的page frame来管理&#xff0c;具体pa…

c++11 标准模板(STL)(std::deque)(八)

定义于头文件 <deque> std::deque 修改器 擦除元素 std::deque<T,Allocator>::erase iterator erase( iterator pos ); (1)(C11 前) iterator erase( const_iterator pos ); (C11 起) iterator erase( iterator first, iterator last ); (2)(C11 前) iterator …

即时编译助力人大金仓KES分析能力飞跃

随着数字化技术对各行各业的不断渗透&#xff0c;人大金仓在金融、能源、电信等行业逐步进入深水区&#xff0c;面临越来越多的核心类系统改造升级&#xff0c;这些系统不仅需要满足在线交易系统运行的高实时性要求&#xff0c;还需要保证高效分析能力以帮助客户进行业务决策。…