1.setState是源码同步更新还是异步更新?
2.ä¸çå°±ä¼çè¶
å®ç¨å°ç»ä»¶ä¹LoadingButton
3.面试官:react中的setState是同步的还是异步的
4.React 的 setState 是同步还是异步?
setState是同步更新还是异步更新?
在React.8之前的版本中,我们更新数据需要用到setState,源码那么你知道setState是源码同步还是异步的呢?它内部是如何实现的,你了解吗?今天我们就一起来学习一下关于setState的源码那些事吧!setState自从React.8添加了Hook后,源码我们编写React组件基本都是源码CCI结合OBV指标源码函数组件,很少用到class组件了。源码我们都知道在函数组件中通过useState这个Hook来修改组件的源码状态,而在.8之前的源码版本中,我们都是源码通过setState来修改组件的状态,因此我们还是源码很有必要了解一下关于setState相关的知识点。
在面试或者工作中,源码我们经常会遇到关于组件状态更新的源码问题,就拿setState来说,源码在组件更新的源码时候,setState是同步更新还是异步更新的?当我们更新一个组件的状态后,我们如何才能立即获取到刚才更新的状态?这些就是我们需要学习和了解的地方。要了解setState是同步还是异步,就先需要了解一下React中的小米电视hdmi源码输出合成事件了。
如果了解过React事件相关方面的童鞋,那么就会知道React的事件都是合成事件,而不是js的原生事件。那什么是合成事件呢?简单来说就是React通过给document上挂载一个事件监听函数,通过事件冒泡的方式来完成事件的执行,当DOM元素触发后会冒泡到document,而React就会找到对应的组件生成一个合成事件出来,并按组件树模拟一遍事件冒泡,这就是React中的合成事件。
当然,上述的方法在React之后的版本中进行了修改。React中将事件挂载在了DOM的容器中,也就是挂载在ReactDOM执行的节点上,这样修改的好处是哪怕一个项目中有多个版本的React存在,组件的事件也不会乱套。那么哪些事件会被捕获生成合成事件呢?在React源码的事件快照中所包含的事件才会被捕获到,例如:click、blur、协成无线认证源码focus等等。
了解完合成事件,我们该继续学习setState是同步还是异步的。一般来说setState是异步的,例如下面这个例子:
clsssTestextendsComponent{ state={ count:0};componentDidMount(){ this.setState({ count:1},()=>{ console.log(this.state.count);//1});console.log(this.state.count);//0}render(){ return(...)}}当我们在componentDidMount生命周期中通过setState修改组件的状态后,我们只有在setState的第二个参数中才能立即获取到当前修改的值,而在外部获取到的值还是未改变之前的值,由此可以证明setState是异步的。我们是否会觉得React中的setState执行像是一个队列,因为React会根据队列逐一执行,并且合并setState的操作,当state数据完成后执行回调,然后根据结果来更新虚拟DOM触发渲染。那么为什么React官方团队要按这样的执行思路来实现呢?为什么不能用同步执行的思路来实现呢?
在React出来后,官方给出的解释的是:为了保持内部的一致性,如果setState是同步的,但是props却不是,就会导致数据的DH算法及源码解读错乱;第二点就是为了后续的升级启用并发更新。那么什么情况下setState是同步的呢?
如果我们将setState放在setTimeout或者setInterval中,那么它们的执行就会跟上面将的完全不同了,大致的代码如下:
clsssTestextendsComponent{ state={ count:0};componentDidMount(){ this.setState({ count:this.state.count+1});console.log(this.state.count);//0setTimeout(()=>{ this.setState({ count:this.state.count+1});console.log('setTimeout',this.state.count);//1},0);}render(){ return(...)}}当setState执行时,会将状态存入padding队列中,然后React会判断当前是否处于batchupdate阶段,如果是,则会将组件存入dirtyComponents中;反之则会遍历所有的dirtyComponents,并调用updateComponent用于更新pending、state或者props。
在React的生命周期事件和合成事件中可以获取到isBatchingUpdates的控制权,用于将状态放入队列中,并控制执行的节奏。而在setTimeout、addEventListener这些原生事件中,无法获取到isBatchingUpdates的控制权,就会导致isBatchingUpdates只会为false,且会一直执行,因此setState就会同步执行并修改组件的国外仿吃鸡源码状态。
最后setState其实并不是真的异步,只是看起来像是异步执行的,它是通过isBatchingUpdates来判断当前执行是同步还是异步的,如果isBatchingUpdates为true,则按异步执行,反之就是同步执行。要改变isBatchingUpdates,只需要打破React的合成事件,在js的原生事件中执行setState即可,所以你知道setState是同步还是异步的吗?
ä¸çå°±ä¼çè¶ å®ç¨å°ç»ä»¶ä¹LoadingButton
ç»ä»¶èæ¯
å¨å¹³æ¶çå·¥ä½ä¸ï¼ç»å¸¸ä¼éå°ä¸ä¸ªåºæ¯ï¼
ç¹å»æé®æ¶è¯·æ±ä¸äºæ¥å£æ°æ®ï¼è为äºé¿å ç¨æ·éå¤çç¹å»æ们é常ä¼ä¸ºè¿äºæé®æ·»å loadingãè¿ä¸ªæ·»å loadingçåè½æ¬èº«æ¶é常ç®åçï¼åªè¦æ们å®ä¹ä¸ä¸ªåé使ç¨å¨Buttonç»ä»¶ä¸å³å¯ï¼ä½å¨ååå°ç®¡ç类项ç®æ¶ï¼è¿æ ·çæé®å¯è½ä¼æé常é常å¤ï¼å¯è½ä¸ä¸ªç»ä»¶ä¸ï¼å¾å¤åéé½æ¯xxx_loadingï¼èæ¶èååä¸å¤ä¼é ãæ¥ä¸æ¥ï¼æ们对Buttonç»ä»¶åä¸ä¸ªç®åçå°è£ æ¥è§£å³è¿ä¸ªèæ¶èååä¸å¤ä¼é çloadingé®é¢
çµææ¥æºæ们å¨ä½¿ç¨AntdçModal对è¯æ¡æ¶ï¼å½æ们çonOk为å¼æ¥å½æ°æ¶ï¼æ¤æ¶Modalçç¡®å®æé®ä¼èªå¨æ·»å loadingææï¼å¨å½æ°æ§è¡å®æåå ³éå¼¹çªï¼å°±åè¿æ ·ï¼æ¤æ¶ï¼ä»£ç å¦ä¸ï¼
asyncFunc(){ returnnewPromise(resolve=>{ setTimeout(()=>{ resolve()},)})},handleTestModal(){ constthat=thisthis.$confirm({ title:'æµè¯å¼æ¥å½æ°',content:'å¼æ¥å½æ°å»¶è¿ä¸¤ç§ç»æ',asynconOk(){ awaitthat.asyncFunc()}})},çå°è¿ç§ææåï¼å°±æ³å°ï¼å¦æå¯ä»¥å°è£ ä¸ä¸ªButtonç»ä»¶ï¼å°éè¦æ§è¡çå½æ°ä¼ å ¥ï¼ç»ä»¶ä¸èªå¨æ ¹æ®å½æ°æ§è¡æ åµæ·»å loadingææå²ä¸æ¯é常çæ¹ä¾¿ã
å®ç°LoadingButtonå®ä¹ç»ä»¶åæ°è¿è¾¹å°±å®ä¹å 个大家ä¼å¸¸ç¨å°çåæ°ï¼text(æé®æå)ãtype(æé®ç±»å)ãasyncFunc(æé®ç¹å»æ¶æ§è¡çå¼æ¥å½æ°)ãdelay(loading延è¿)ï¼å¦å¤ï¼è¿éè¦ä¸ä¸ªç»ä»¶å é¨çloadingåéæ¥æ§å¶æ们Buttonç»ä»¶çç¶æï¼ä»£ç å¦ä¸ï¼
exportdefault{ data(){ return{ loading:false}},props:{ text:{ type:String,default:'ç¡®å®'},type:{ type:String,default:'primary'},delay:{ type:Number,default:0},asyncFunc:{ type:Function,default:()=>{ }}},}使ç¨antdä¸çButtonç»ä»¶è¿è¡äºæ¬¡å°è£å¨æ们çèªå®ä¹LoadingButtonç»ä»¶ä¸ï¼å°ä¸é¢å®ä¹çåæ°ä½¿ç¨èµ·æ¥ï¼å¹¶ç»å®ä¸ä¸ªclickäºä»¶ï¼ä»£ç å¦ä¸ï¼
<template><Button:type="type":loading="loading"@click="handleClick">{ { text}}</Button></template><script>import{ Button}from'ant-design-vue'exportdefault{ components:{ Button},methods:{ handleClick(){ }}}</script>å¤æå¼æ¥å½æ°asyncFuncè¿ä¸é¨å为æ´ä¸ªç»ä»¶æéè¦çä¸ä¸ªé¨åï¼å³æ们å¦ä½å»å¤æä¼ å ¥çå½æ°æ¯å¼æ¥å½æ°ï¼å½æä»¬ä¼ å ¥çasyncFuncå½æ°æ¯å¼æ¥å½æ°æ¶ï¼ç»ä»¶æéè¦æ·»å loadingçå¨ç»ï¼é£ä¹æ们åºè¯¥å¦ä½å»å¤æä¸ä¸ªå½æ°æ¯å¦ä¸ºå¼æ¥å½æ°å¢ï¼
åèantdæ¯å¦ä½å®ç°çï¼ä¸é¢æ们åä»ç»äºantdçModal对è¯æ¡ä¸æ类似çé»è¾ï¼é£ä¹ä¸å¦¨å»é 读ä¸ä¸è¿é¨åç¸å ³çæºç ï¼çä¸antdçå®ç°æ¹å¼ï¼
//components/modal/ActionButton.jsxonClick(){ const{ actionFn,closeModal}=this;if(actionFn){ letret;if(actionFn.length){ ret=actionFn(closeModal);}else{ ret=actionFn();if(!ret){ closeModal();}}if(ret&&ret.then){ this.setState({ loading:true});ret.then((...args)=>{ //It'sunnecessarytosetloading=false,fortheModalwillbeunmountedafterclose.//this.setState({ loading:false});closeModal(...args);},e=>{ //Emiterrorwhencatchpromisereject//eslint-disable-next-lineno-consoleconsole.error(e);//See:/post/面试官:react中的setState是同步的还是异步的
在面试过程中,经常会遇到关于React中setState操作同步或异步的问题。下面通过几个例子来解答这个问题:
例子1:点击按钮触发更新,在handle函数中调用两次setState。
例子2:在setTimeout回调中执行例子1的两次setState操作。
例子3:使用unstable_batchedUpdates在setTimeout回调中执行,unstable_batchedUpdates的回调函数中调用两次setState。
例子4:两次setState在setTimeout回调中执行,但使用concurrent模式启动,即通过调用ReactDOM.unstable_createRoot启动应用。
简单来说,在同一个上下文中触发多次更新,这些更新会被合并为一次更新,例如在之前的React版本中,如果脱离当前的上下文,则不会被合并。原因是,处于同一个上下文中的多次setState操作的executionContext都会包含BatchedContext,包含BatchedContext的setState操作会合并。当executionContext等于NoContext时,就会同步执行SyncCallbackQueue中的任务,因此setTimeout中的多次setState操作不会合并,且会同步执行。
在Concurrent mode下,上面的例子也会合并为一次更新,原因在于简化源码中,多次setState操作会比较这些操作的优先级,如果优先级一致,则会先返回,不会进行后面的渲染阶段。
总结:
在legacy模式下:命中batchedUpdates时是异步,未命中batchedUpdates时是同步的。
在concurrent模式下:都是异步的。
如需高效学习,可观看视频讲解,了解往期React源码解析文章,涵盖React设计、源码架构、核心API、legacy与concurrent模式、Fiber架构、渲染阶段、diff算法、commit阶段、生命周期、状态更新流程、hooks源码、手写hooks、scheduler与Lane、concurrent模式、context、事件系统、手写迷你版React等详细内容。
React 的 setState 是同步还是异步?
setState 是同步还是异步?
代码示例表明,setState 呈现异步特性。在 setTimeout 内修改两次 state 后打印,结果为两次 0,说明 state 立即修改,之后每次渲染,结果为 0、1、2,证实了 setState 的同步性。
疑惑继续,代码进一步验证,setState 的行为不一致,打印结果为三次 0,仅触发一次渲染,说明 setState 确实为异步。
类组件和函数组件的 setState 都遵循异步模式,修改 state 时,渲染仅触发一次,说明状态更新与渲染之间存在延迟。
深入源码探索,React 渲染流程揭示了关键。vdom 转换为 fiber 的过程是可打断的,而 fiber 更新 DOM 的 commit 阶段是同步的。这解释了 setState 的异步行为。
setState 的执行依赖于执行环境的 context,批量更新会立即触发渲染,而非批量则延迟至下次更新。
setTimeout 内的 setState 会触发即时渲染,这与批量更新的 context 无关。通过设置 batchUpdates API 可实现批量执行,但这需要额外操作。
React 引入了 createRoot API,使得所有 setState 操作默认为异步批量执行,解决了这个问题。
综上所述,setState 的同步异步特性取决于其执行环境和 React 版本。在 React 中,所有操作默认为异步批量执行,将消除同步异步的讨论。