react中setState是同步的还是异步的

首先说一下setState是同步的还是异步的?

1.解读 setState 工作流

在这里插入图片描述
 接下来我们就沿着这个流程,逐个在源码中对号入座。首先是 setState 入口函数:

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

入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去。这里我们以对象形式的入参为例,可以看到它直接调用了 this.updater.enqueueSetState这个方法:

enqueueSetState: function (publicInstance, partialState) {// 根据 this 拿到对应的组件实例var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');// 这个 queue 对应的就是一个组件实例的 state 数组var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);queue.push(partialState);//  enqueueUpdate 用来处理当前的组件实例enqueueUpdate(internalInstance);
}

将新的 state 放进组件的状态队列里;
用 enqueueUpdate 来处理将要更新的实例对象。

继续往下走,看看 enqueueUpdate 做了什么:

function enqueueUpdate(component) {ensureInjected();// 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段if (!batchingStrategy.isBatchingUpdates) {// 若当前没有处于批量创建/更新组件的阶段,则立即更新组件batchingStrategy.batchedUpdates(enqueueUpdate, component);return;}// 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”dirtyComponents.push(component);if (component._updateBatchNumber == null) {component._updateBatchNumber = updateBatchNumber + 1;}
}

这个 enqueueUpdate非常有嚼头,它引出了一个关键的对象——batchingStrategy,该对象所具备的isBatchingUpdates属性直接决定了当下是要走更新流程,还是应该排队等待;其中的batchedUpdates方法更是能够直接发起更新流程。由此可以大胆推测,batchingStrategy 或许正是 React 内部专门用于管控批量更新的对象。

接下来,我们就一起来研究研究这个 batchingStrategy。

/*** batchingStrategy源码
**/
var ReactDefaultBatchingStrategy = {// 全局唯一的锁标识isBatchingUpdates: false,// 发起更新动作的方法batchedUpdates: function(callback, a, b, c, d, e) {// 缓存锁变量var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates// 把锁“锁上”ReactDefaultBatchingStrategy. isBatchingUpdates = trueif (alreadyBatchingStrategy) {callback(a, b, c, d, e)} else {// 启动事务,将 callback 放进事务里执行transaction.perform(callback, null, a, b, c, d, e)}}
}

batchingStrategy对象并不复杂,你可以理解为它是一个“锁管理器”。

里的“锁”,是指 React 全局唯一的 isBatchingUpdates变量,isBatchingUpdates的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。

理解了批量更新整体的管理机制,还需要注意 batchedUpdates中,有一个引人注目的调用:

transaction.perform(callback, null, a, b, c, d, e)

这行代码为我们引出了一个更为硬核的概念——React 中的 Transaction(事务)机制

理解 React 中的 Transaction(事务) 机制。

Transaction 在 React 源码中的分布可以说非常广泛。如果在 Debug React 项目的过程中,发现函数调用栈中出现了 initialize、perform、close、closeAll或者 notifyAll这样的方法名,那么很可能当前就处于一个 Trasaction中。

Transaction 在 React 源码中表现为一个核心类,React 官方曾经这样描述它:Transaction 是创建一个黑盒,该黑盒能够封装任何的方法。因此,那些需要在函数运行前、后运行的方法可以通过此方法封装(即使函数运行中有异常抛出,这些固定的方法仍可运行),实例化 Transaction 时只需提供相关的方法即可。

这段话初读有点拗口,结合 React 源码中的一段针对 Transaction 的注释来理解它。

* <pre>*                       wrappers (injected at creation time)*                                      +        +*                                      |        |*                    +-----------------|--------|--------------+*                    |                 v        |              |*                    |      +---------------+   |              |*                    |   +--|    wrapper1   |---|----+         |*                    |   |  +---------------+   v    |         |*                    |   |          +-------------+  |         |*                    |   |     +----|   wrapper2  |--------+   |*                    |   |     |    +-------------+  |     |   |*                    |   |     |                     |     |   |*                    |   v     v                     v     v   | wrapper*                    | +---+ +---+   +---------+   +---+ +---+ | invariants* perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->*                    | |   | |   |   |         |   |   | |   | |*                    | |   | |   |   |         |   |   | |   | |*                    | |   | |   |   |         |   |   | |   | |*                    | +---+ +---+   +---------+   +---+ +---+ |*                    |  initialize                    close    |*                    +-----------------------------------------+* </pre>

说白了,Transaction就像是一个“壳子”,它首先会将目标函数用 wrapper(一组 initialize及 close方法称为一个 wrapper) 封装起来,同时需要使用 Transaction 类暴露的 perform方法去执行它。如上面的注释所示,在 anyMethod执行之前,perform会先执行所有 wrapper 的 initialize方法,执行完后,再执行所有 wrapper的 close方法。这就是 React 中的事务机制。

“同步现象”的本质

下面结合对事务机制的理解,继续来看在 ReactDefaultBatchingStrategy这个对象。ReactDefaultBatchingStrategy其实就是一个批量更新策略事务,它的 wrapper有两个:FLUSH_BATCHED_UPDATES和 RESET_BATCHED_UPDATES

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];

把这两个 wrapper套进 Transaction的执行机制里,不难得出一个这样的流程:

在callback执行完成后,RESET_BATCHED_UPDATES将isBatchingUpdates置为false,FLUSH_BATCHED_UPDATES执行flusfBatchedUpdates,然后里面会循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法(componentWillReceiveProps=>shouldComponentUpdate=>componentWillUpdate=>render=>componentDidUpdate),最后实现组件更新。

到这里,相信已经对 isBatchingUpdates管控下的批量更新机制已经了然于胸。但是 setState为何会表现同步这个问题,似乎还是没有从当前展示出来的源码里得到根本上的回答。这是因为 batchedUpdates这个方法,不仅仅会在 setState之后才被调用。若我们在 React 源码中全局搜索 batchedUpdates,会发现调用它的地方很多,但与更新流有关的只有这两个地方:

// ReactMount.js
_renderNewRootComponent: function( nextElement, container, shouldReuseMarkup, context ) {// 实例化组件var componentInstance = instantiateReactComponent(nextElement);// 初始渲染直接调用 batchedUpdates 进行同步渲染ReactUpdates.batchedUpdates(batchedMountComponentIntoNode,componentInstance,container,shouldReuseMarkup,context);...
}

这段代码是在首次渲染组件时会执行的一个方法,我们看到它内部调用了一次 batchedUpdates,这是因为在组件的渲染过程中,会按照顺序调用各个生命周期函数。开发者很有可能在声明周期函数中调用 setState。因此,我们需要通过开启 batch来确保所有的更新都能够进入 dirtyComponents里去,进而确保初始渲染流程中所有的 setState都是生效的。

下面代码是 React 事件系统的一部分。当我们在组件上绑定了事件之后,事件中也有可能会触发 setState。为了确保每一次 setState都有效,React同样会在此处手动开启批量更新。

// ReactEventListener.js
dispatchEvent: function (topLevelType, nativeEvent) {...try {// 处理事件ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);} finally {TopLevelCallbackBookKeeping.release(bookKeeping);}
}

话说到这里,一切都变得明朗了起来:isBatchingUpdates这个变量,在 React 的生命周期函数以及合成事件执行前,已经被 React 悄悄修改为了 true,这时我们所做的 setState操作自然不会立即生效。当函数执行完毕后,事务的 close方法会再把 isBatchingUpdates改为 false。

以开头示例中的 increment 方法为例,整个过程像是这样:

  handleIncrement = () => {// 进来先锁上isBatchingUpdates = true;console.log('increment setState前的count', this.state.count);this.setState({count: this.state.count + 1});console.log('increment setState后的count', this.state.count);// 执行完函数再放开isBatchingUpdates = false;};

很明显,在 isBatchingUpdates的约束下,setState只能是异步的。而当 setTimeout从中作祟时,事情就会发生一点点变化:

  handleReduce = () => {// 进来先锁上isBatchingUpdates = true;setTimeout(() => {console.log('reduce setState前的count', this.state.count);this.setState({count: this.state.count - 1});console.log('reduce setState后的count', this.state.count);}, 0);// 执行完函数再放开isBatchingUpdates = false;};

会发现,咱们开头锁上的那个 isBatchingUpdates,对 setTimeout内部的执行逻辑完全没有约束力。因为 isBatchingUpdates是在同步代码中变化的,而 setTimeout的逻辑是异步执行的。当 this.setState调用真正发生的时候,isBatchingUpdates早已经被重置为了 false,这就使得当前场景下的 setState 具备了立刻发起同步更新的能力。所以咱们前面说的没错——setState 并不是具备同步这种特性,只是在特定的情境下,它会从 React 的异步管控中“逃脱”掉。

对整个 setState 工作流做一个总结:

setState并不是单纯同步/异步的,它的表现会因调用场景的不同而不同:在 React 钩子函数及合成事件中,它表现为异步;而在 setTimeout、setInterval等函数中,包括在 DOM 原生事件中,它都表现为同步。这种差异,本质上是由 React 事务机制和批量更新机制的工作方式来决定的。

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

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

相关文章

ThingsBoard初始化数据库Postgres

本章将介绍ThingsBoard的本地环境搭建&#xff0c;以及源码的编译安装。本机环境&#xff1a;centos7&#xff0c;docker,postgres 环境安装 开发环境要求&#xff1a; docker pcentos7 &#xff1b;docker&#xff1b;postgres: 1、使用Docker创建postgres容器化创建 docker…

JS函数知识点(复习)

在 JavaScript&#xff08;JS&#xff09;中使用函数有多种原因&#xff0c;这些原因不仅涉及到代码的组织和可读性&#xff0c;还涉及到代码的可维护性、复用性以及程序的模块化等方面。以下是一些关键的理由&#xff1a; 代码重用&#xff1a;函数允许你将一段代码块封装起来…

基于深度学习的唇语识别系统的设计与实现

概要 人工智能作为三大工程之一&#xff0c;从上个世纪至今仍然活跃于各个行业的研究与应用之中&#xff0c;应时代的热潮方向&#xff0c;本 课题主要针对深度学习技术应用于唇语识别当中&#xff0c;实现词语唇语的翻译功能。唇语识别在图像处理中一直是一个富 有挑战性的课题…

力扣59. 螺旋矩阵 II

思路&#xff1a;此题思路就是绕圈遍历&#xff0c;全靠条件处理技巧&#xff0c;重点要清楚的就是循环不变量&#xff1a;左闭右开&#xff08;即拐弯处的一个数&#xff0c;留给第二行处理&#xff09; 以下是代码随想录的作者的一张图片&#xff0c;每次for循环&#xff0c;…

使用三种方式读取文本文件

文章目录 &#x1f354;需求&#x1f384;思路⭐代码⭐效果&#x1f6f8;注意 在 Java 中&#xff0c;读取文件通常涉及以下几个步骤&#xff1a; 打开文件&#xff1a;首先需要创建一个文件对象&#xff0c;表示要读取的文件。这个文件对象可以包含文件路径、文件名等信息。 …

使用canvas实现图纸标记及回显

图纸 图纸标记后的效果图 最近做的一个qms项目里面&#xff0c;需要前端在图纸上实现标记及标记后的内容还要能够回显&#xff0c;然后后端通过标记的点&#xff0c;去读取标记图纸的内容&#xff0c;如一些公式、数据之类的&#xff0c;目前实现的功能有 在图纸上面进行矩形…

Mysql 索引、锁与MVCC等相关知识点

文章目录 Mysql锁的类型锁使用MVCC快照读和当前读读视图【Read View】串行化的解决 索引类型存储方式区分逻辑区分实际使用区分索引失效情况 索引建立规范SQL编写规范exlpain字段解析ACID的原理日志引擎慢SQL整合SpringBoot博客记录 Mysql锁的类型 MySQL中有哪些锁&#xff1a…

Flutter:构建美观应用的跨平台方案

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

C#创建第一个PIESDK模版的项目

目录 环境配置创建项目方式 环境配置 1软件安装 通过安装光盘或者U盘等介质读取PIE软件的安装程序和使用文档。程序安装过程比较简单&#xff0c;软件本身不借助与任何第三方程序&#xff0c;直接双击安装程序【PIESDK.Net_V6.3_Windows_X64.exe】安装文件&#xff0c;即可安装…

尚硅谷vue全家桶(上)

vue2 简介第一天第二天 第三天第四天第五天 第六天第七天第八天 第九天 网课链接&#xff08;半个月拿下&#xff09; 简介 需要提前会的东西 中文文档链接点下面 vue.js 要会查文档用API 第一天 清除提示1 再文档中下载开发版本浏览器安装vue devtools插件 打开允许访问URL…

【C语言】字符函数与字符串函数以及内存函数 { 超详细攻略,一篇学会 }

今日分享&#xff1a;字符、字符串函数和内存函数 内存函数就是对内存进行操作的函数 字符串函数就是对字符串进行操作的函数 字符函数就是对字符进行操作的函数 str前缀的函数是字符串函数&#xff0c;头文件string.h mem前缀的函数是内存函数&#xff0c;头文件stdlib.h 字符…

25考研数据结构复习·3.3.2栈和队列的应用——表达式求值

三种算术表达式 中缀表达式 ((15/(7-(11)))*3)-(2(11)) 由三个部分组成&#xff1a;操作数、运算符、界限符 运算符在两个操作数中间&#xff1a;ab&#xff1b;ab-c&#xff1b;ab-c*d ❗后缀表达式 逆波兰表达式 运算符在两个操作数后面&#xff1a;ab&#xff1b;abc-或ab…

python知识点总结(二)

这里写目录标题 1、什么是解释性语言&#xff0c;什么是编译性语言&#xff1f;2、说说中作用域是怎么划分的3、type和isinstance方法的区别4、浅拷贝和深拷贝5、python中变量在内存中存储方式6、python中的封装、继承、多态7、python中内存管理机制是怎么样的&#xff1f;8、简…

旋转中心 机械手抓料方式

一、为什么要计算旋转中心&#xff1f; 机器视觉——旋转中心的标定_旋转标定-CSDN博客 在机械手抓料的时候传送带上过来的料可能是各个角度的&#xff0c;不同的位置&#xff0c;这样如果我们没有做好机械手标定的话很难抓取&#xff0c;因此我们要做旋转中和和机械手TCP标定…

章节2:单词本该这样记

为什么我们记不住单词&#xff1f; 单词不是被胡编乱造出来的&#xff0c;单词是有规律的&#xff0c;单词是符合人类的逻辑的。 单词实际意思结构意义历史文化 我们要怎么记单词&#xff1f; 掌握单词的结构规律了解与单词有关的历史文化灵活巧计&#xff0c;不要太拘泥于…

唯一约束

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 唯一约束 唯一约束的特点是在某一个列上的内容不允许出现重复。 例如&#xff0c;现在要收集用户的信息&#xff0c;假设包含编号&#xff08;mid&#xff09;、姓名&…

Word2vec 学习笔记

word2vec 学习笔记 0. 引言1. Word2vec 简介1-1. CBOW1-2. SG 2. 实战 0. 引言 最近研究向量检索&#xff0c;看到有同事使用 MeCab、Doc2Vec&#xff0c;所以把 Word2vec 这块知识学习一下。 1. Word2vec 简介 Word2vec 即 word to vector&#xff0c;顾名思义&#xff0c;…

封装哈希表

本文旨在讲解哈希表的封装&#xff0c;我们以哈希桶的结构来进行封装unorderedmap/set。要想实现封装哈希表&#xff0c;我们首先得先将哈希表的结构给搭建出来&#xff0c;然后再根据哈希桶的结构进一步封装unorderedmap/set&#xff01; 下面我们先来实现哈希桶的结构&#x…

Internet Download Manager(IDM下载) v6.42.3 绿色版介绍

互联网下载管理器是一个广泛使用的软件&#xff0c;它可以帮助用户更好地管理和加速他们的下载。最新版本v6.42.3已经发布&#xff0c;它带来了一系列新功能和改进&#xff0c;让用户更加方便和快速地下载他们需要的文件。 新版本的互联网下载管理器增加了对最新浏览器的支持&…

1.Spring入门

1.1 Spring简介 Spring是一个轻量级Java 企业级应用程序开发框架&#xff0c;目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/EEfull-stack(一站式) 轻量级开源框架&#xff0c;为开发Java应用程序提供全面的基础架构支持。 Spring Fra…