1.面试官:react中的源码setState是同步的还是异步的
2.ä¸çå°±ä¼çè¶
å®ç¨å°ç»ä»¶ä¹LoadingButton
3.React 的 setState 是同步还是异步?
4.在Flutter中使用setState时的6个简单技巧
5.setState是同步更新还是异步更新?
面试官:react中的setState是同步的还是异步的
在面试过程中,经常会遇到关于React中setState操作同步或异步的源码问题。下面通过几个例子来解答这个问题:
例子1:点击按钮触发更新,源码在handle函数中调用两次setState。源码
例子2:在setTimeout回调中执行例子1的源码两次setState操作。
例子3:使用unstable_batchedUpdates在setTimeout回调中执行,源码AI朗读源码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、zxing c#源码事件系统、手写迷你版React等详细内容。
ä¸çå°±ä¼çè¶ å®ç¨å°ç»ä»¶ä¹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 是同步还是异步?
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 中,所有操作默认为异步批量执行,将消除同步异步的讨论。
在Flutter中使用setState时的6个简单技巧
setState函数是在Flutter应用程序中管理状态的最基本方法。以下是一些保持应用可维护性的最佳实践。StatefulWidget的setState函数是一种在Flutter应用程序中管理状态的简单方法。但是,当您希望您的应用程序正常工作和高性能时,您需要避免几个陷阱。以下是您应该坚持的一些最佳实践。
setState有什么用?setState是上架+审核+源码Flutter发出rebuild(重建)当前widget及其后代的方式。在rebuild过程中,最新的变量值将被用于创建用户界面。比方说,一个用户将一个开关从打开切换到关闭。该开关有一个存储该值的支持变量,所以在改变之后,它被设置为false。开关本身并不反映这一变化,直到它被重建为新的支持字段值。
更改值
调用setState()
用户界面已更新
?技巧1:保持##widgets小!setState触发了对你当前所在的小组件的重建。如果你的整个应用程序只包含一个widget,那么整个widget将被重建,这将使你的应用程序变得缓慢。请看下面的例子。
import'package:flutter/material.dart';classHomeextendsStatefulWidget{ constHome({ Key?key}):super(key:key);@overrideState<Home>createState()=>_State();}class_StateextendsState<Home>{ bool_tile1=false;bool_tile2=false;bool_tile3=false;bool_tile4=false;bool_tile5=false;@overrideWidgetbuild(BuildContextcontext){ print("builtmethodHome");//<--setStatetriggersbuildhere!returnScaffold(appBar:AppBar(title:constText("Demo")),body:Center(child:Column(crossAxisAlignment:CrossAxisAlignment.center,mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile1?"on":"off"}"),value:_tile1,onChanged:(_){ setState((){ _tile1=!_tile1;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile2?"on":"off"}"),value:_tile2,onChanged:(_){ setState((){ _tile2=!_tile2;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile3?"on":"off"}"),value:_tile3,onChanged:(_){ setState((){ _tile3=!_tile3;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile4?"on":"off"}"),value:_tile4,onChanged:(_){ setState((){ _tile4=!_tile4;});}),SwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _tile5?"on":"off"}"),value:_tile5,onChanged:(_){ setState((){ _tile5=!_tile5;});})])));}}这里我们在一个Column中有5个SwitchListTile小部件,它们都是同一个小部件的一部分。
如果您切换任何控件,整个屏幕都会被重建。Scaffold,AppBar,Column,...但只要重建已更改的小部件就足够了。让我们看下一个代码示例:
import'package:flutter/material.dart';classHome2extendsStatefulWidget{ constHome2({ Key?key}):super(key:key);@overrideState<Home2>createState()=>_State();}class_StateextendsState<Home2>{ @overrideWidgetbuild(BuildContextcontext){ print("builtmethodHome2");returnScaffold(appBar:AppBar(title:constText("Demo")),body:Center(child:Column(crossAxisAlignment:CrossAxisAlignment.center,mainAxisAlignment:MainAxisAlignment.center,children:const<Widget>[Switch(),Switch(),Switch(),Switch(),Switch()])));}}classSwitchextendsStatefulWidget{ constSwitch({ Key?key}):super(key:key);@overrideState<StatefulWidget>createState()=>_SwitchState();}class_SwitchStateextendsState<Switch>{ bool_value=false;@overrideWidgetbuild(BuildContextcontext){ print("buildmethodSwitch");//<--setStatetriggersbuildhere!returnSwitchListTile(activeColor:Colors.green,inactiveThumbColor:Colors.red,title:Text("Switchis${ _value?"on":"off"}"),value:_value,onChanged:(_){ setState((){ _value=!_value;});});}}在这里,我们将SwitchListTile包装在单个StatefulWidget中。页面看起来相同,但如果您单击此示例中的任何开关,则只有单击的小部件将重建。
?技巧2:不要在构建方法中调用setState来自FlutterAPI文档
这个方法有可能在每一帧中被调用,除了建立一个小部件外,不应该有任何副作用。
build方法旨在构建小部件树,因此我们应该保持这种方式。不要在这里做花哨的事情,它会减慢你的应用程序。对setState的调用可能会触发额外的重建,在最坏的情况下,你可能最终会出现一个异常,告诉你目前有一个重建正在进行。
?技巧3:不要在initState方法中调用setStateinitState将在完成后触发重建,因此无需在此方法中调用setState。此方法旨在初始化与状态相关的属性,例如设置默认值或订阅流。不要在这里做任何其他事情!
?技巧4:setState()和setState(...)是相等的像这样使用setState没关系
setState((){ _text=“Hello”;});或者像这样
_text=“Hello”;setState((){ });结果是一样的。
?技巧5:setState(...)代码必须很小不要在setState内做任何大的计算,因为它将阻止你的应用程序重绘。请看下面的示例代码:
setState((){ for(vari=0;i<;i++)print(i);_value=true;});只有在打印语句之后,小部件才会重建。在这段时间里,你的应用程序不会对用户的操作做出反应,它将在之后执行这些操作。因此,如果用户因为没有视觉反馈而多次点击一个控件,多次重建就会堆积起来,会使应用程序的速度更慢。
一个更好的方法是在执行一个长期运行的操作时显示一个进度指示器,这样用户就知道正在发生一些事情,他需要等待完成。
?技巧6:setState(...)代码不能是异步的运行代码时
setState(()async{ awaitFuture.delayed(constDuration(seconds:5));});你最终会得到一个类似这样的异常信息:
在方法之外执行异步操作,然后调用它。
结束我希望这些见解能帮助你更好地理解Flutter中setState的机制。坚持这些技巧,你会有更少的问题和更快的应用程序。源代码例子可以在GitHub上找到。
原文:/post/
setState是同步更新还是异步更新?
在React.8之前的版本中,我们更新数据需要用到setState,那么你知道setState是同步还是异步的呢?它内部是如何实现的,你了解吗?今天我们就一起来学习一下关于setState的那些事吧!setState自从React.8添加了Hook后,我们编写React组件基本都是函数组件,很少用到class组件了。我们都知道在函数组件中通过useState这个Hook来修改组件的状态,而在.8之前的版本中,我们都是通过setState来修改组件的状态,因此我们还是很有必要了解一下关于setState相关的知识点。
在面试或者工作中,我们经常会遇到关于组件状态更新的问题,就拿setState来说,在组件更新的时候,setState是同步更新还是异步更新的?当我们更新一个组件的状态后,我们如何才能立即获取到刚才更新的状态?这些就是我们需要学习和了解的地方。要了解setState是同步还是异步,就先需要了解一下React中的合成事件了。
如果了解过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却不是,就会导致数据的错乱;第二点就是为了后续的升级启用并发更新。那么什么情况下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是同步还是异步的吗?