1.在Flutter中使用setState时的源码6个简单技巧
2.这些hook更优雅的管理你的状态
3.面试官:react中的setState是同步的还是异步的
4.React 的 setState 是同步还是异步?
在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中。页面看起来相同,但如果您单击此示例中的dll时间源码编辑任何开关,则只有单击的小部件将重建。
?技巧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;});只有在打印语句之后,小部件才会重建。在这段时间里,你的应用程序不会对用户的操作做出反应,它将在之后执行这些操作。因此,如果用户因为没有视觉反馈而多次点击一个控件,多次重建就会堆积起来,会使应用程序的速度更慢。
一个更好的方法是在执行一个长期运行的操作时显示一个进度指示器,这样用户就知道正在发生一些事情,2017vip 源码他需要等待完成。
?技巧6:setState(...)代码不能是异步的运行代码时
setState(()async{ awaitFuture.delayed(constDuration(seconds:5));});你最终会得到一个类似这样的异常信息:
在方法之外执行异步操作,然后调用它。
结束我希望这些见解能帮助你更好地理解Flutter中setState的机制。坚持这些技巧,你会有更少的问题和更快的应用程序。源代码例子可以在GitHub上找到。
原文:/post/
这些hook更优雅的管理你的状态
本文是深入浅出ahooks源码系列文章的第十二篇,这个系列的目标主要有以下几点:加深对Reacthooks的理解。
学习如何抽象自定义hooks。构建属于自己的Reacthooks工具库。
培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
今天我们来聊聊ahooks中那些可以帮助我们更优雅管理我们state(状态)的那些hook。一些比较特殊的,比如cookie/localStorage/sessionStorage,useUrlState等,我们已经单独拿出来细讲了,感兴趣可以看看笔者的历史文章。
useSetState管理object类型state的Hooks,用法与class组件的this.setState基本一致。
先来了解一下可变数据和不可变数据的含义和区别如下:
可变数据(mutable)即一个数据被创建之后,可以随时进行修改,修改之后会影响到原值。
不可变数据(Immutable)就是一旦创建,就不能再被更改的数据。对Immutable对象的任何修改或添加删除操作都会返回一个新的Immutable对象。
我们知道,ReactFunctionComponents中的State是不可变数据。所以我们经常需要写类似如下的代码:
setObj((prev)=>({ ...prev,name:'Gopal',others:{ ...prev.others,age:'',}}));通过useSetState,可以省去对象扩展运算符操作这个步骤,彩票平台开源码即:
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));其内部实现也比较简单,如下所示:
调用设置值方法的时候,会根据传入的值是否为函数。如果是函数,则入参为旧状态,输出新的状态。否则直接作为新状态。这个符合setState的使用方法。
使用对象拓展运算符,返回新的对象,保证原有数据不可变。
constuseSetState=<SextendsRecord<string,any>>(initialState:S|(()=>S),):[S,SetState<S>]=>{ const[state,setState]=useState<S>(initialState);//合并操作,并返回一个全新的值constsetMergeState=useCallback((patch)=>{ setState((prevState)=>{ //新状态constnewState=isFunction(patch)?patch(prevState):patch;//也可以通过类似Object.assign的方式合并//对象拓展运算符,返回新的对象,保证原有数据不可变returnnewState?{ ...prevState,...newState}:prevState;});},[]);return[state,setMergeState];};可以看到,其实就是将对象拓展运算符的操作封装到内部。
还有其他更优雅的方式?我们可以使用use-immer
useImmer(initialState)非常类似于useState。该函数返回一个元组,元组的第一个值是当前状态,第二个是updater函数,它接受一个immerproducer函数或一个值作为参数。
使用如下:
const[person,updatePerson]=useImmer({ name:"Michel",age:});functionupdateName(name){ updatePerson(draft=>{ draft.name=name;});}functionbecomeOlder(){ updatePerson(draft=>{ draft.age++;});}当向更新函数传递一个函数的时候,draft参数可以自由地改变,直到producer函数结束,所做的改变将是不可变的,并成为下一个状态。这更符合我们的使用习惯,可以通过draft.xx.yy的方式更新我们对象的值。
useBoolean和useToggle这两个都是特殊情况下的值管理。
useBoolean,优雅的asp生成pdf源码管理boolean状态的Hook。
useToggle,用于在两个状态值间切换的Hook。
实际上,useBoolean又是useToggle的一个特殊使用场景。
先看useToggle。
这里使用了typescript函数重载声明入参和出参类型,根据不同的入参会返回不同的结果。比如第一个入参为boolean布尔值,则返回一个元组,第一项为boolean值,第二个为更新函数。优先级从上到下依次变低。
入参可能有两个值,第一个为默认值(认为是左值),第二个是取反之后的值(认为是右值),可以不传,不传的时候,则直接根据默认值取反!defaultValue。
toggle函数。切换值,也就是上面的左值和右值的转换。
set。直接设置值。
setLeft。设置默认值(左值)。
setRight。如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的取反值。
//TS函数重载的使用functionuseToggle<T=boolean>():[boolean,Actions<T>];functionuseToggle<T>(defaultValue:T):[T,Actions<T>];functionuseToggle<T,U>(defaultValue:T,reverseValue:U):[T|U,Actions<T|U>];functionuseToggle<D,R>(//默认值defaultValue:D=falseasunknownasD,//取反reverseValue?:R,){ const[state,setState]=useState<D|R>(defaultValue);constactions=useMemo(()=>{ constreverseValueOrigin=(reverseValue===undefined?!defaultValue:reverseValue)asD|R;//切换stateconsttoggle=()=>setState((s)=>(s===defaultValue?reverseValueOrigin:defaultValue));//修改stateconstset=(value:D|R)=>setState(value);//设置为defaultValueconstsetLeft=()=>setState(defaultValue);//如果传入了reverseValue,则设置为reverseValue。否则设置为defautValue的反值constsetRight=()=>setState(reverseValueOrigin);return{ toggle,set,setLeft,setRight,};//useToggleignorevaluechange//},[defaultValue,reverseValue]);},[]);return[state,actions];}而useBoolean是对useToggle的一个使用。如下,比较简单,不细说
exportdefaultfunctionuseBoolean(defaultValue=false):[boolean,Actions]{ const[state,{ toggle,set}]=useToggle(defaultValue);constactions:Actions=useMemo(()=>{ constsetTrue=()=>set(true);constsetFalse=()=>set(false);return{ toggle,set:(v)=>set(!!v),setTrue,setFalse,};},[]);return[state,actions];}usePrevious保存上一次状态的Hook。
其原理,是每次状态变更的时候,比较值有没有发生变化,变更状态:
维护两个状态prevRef(保存上一次的状态)和curRef(保存当前状态)。
状态变更的时候,使用shouldUpdate判断是否发生变化,默认通过Object.is判断。开发者可以自定义shouldUpdate函数,并决定什么时候记录上一次状态。
状态发生变化,更新prevRef的值为上一个curRef,并更新curRef为当前的状态。
constdefaultShouldUpdate=<T>(a?:T,b?:T)=>!Object.is(a,b);functionusePrevious<T>(state:T,shouldUpdate:ShouldUpdateFunc<T>=defaultShouldUpdate,):T|undefined{ //使用了useRef的特性,一直保持引用不变//保存上一次值constprevRef=useRef<T>();//当前值constcurRef=useRef<T>();//自定义是否更新上一次的值if(shouldUpdate(curRef.current,state)){ prevRef.current=curRef.current;curRef.current=state;}returnprevRef.current;}useRafState只在requestAnimationFramecallback时更新state,一般用于性能优化。
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
假如你的操作是比较频繁的,就可以通过这个hook进行性能优化。
重点看setRafState方法,它执行的时候,会取消上一次的setRafState操作。重新通过requestAnimationFrame去控制setState的执行时机。
另外在页面卸载的时候,会直接取消操作,避免内存泄露。
functionuseRafState<S>(initialState?:S|(()=>S)){ constref=useRef(0);const[state,setState]=useState(initialState);constsetRafState=useCallback((value:S|((prevState:S)=>S))=>{ cancelAnimationFrame(ref.current);ref.current=requestAnimationFrame(()=>{ setState(value);});},[]);//unMount的时候,去除监听useUnmount(()=>{ cancelAnimationFrame(ref.current);});return[state,setRafState]asconst;}useSafeState用法与React.useState完全一样,但是在组件卸载后异步回调内的setState不再执行,避免因组件卸载后更新状态而导致的内存泄漏。
代码如下:
在更新的时候,通过useUnmountedRef判断如果组件卸载,则停止更新。
functionuseSafeState<S>(initialState?:S|(()=>S)){ //判断是否卸载constunmountedRef=useUnmountedRef();const[state,setState]=useState(initialState);constsetCurrentState=useCallback((currentState)=>{ //如果组件卸载,则停止更新if(unmountedRef.current)return;setState(currentState);},[]);return[state,setCurrentState]asconst;}useUnmountedRef这个我们之前提过,简单回顾下,其实就是在hook的返回值中标记组件为已卸载。
constuseUnmountedRef=()=>{ constunmountedRef=useRef(false);useEffect(()=>{ unmountedRef.current=false;//如果已经卸载,则会执行return中的逻辑return()=>{ unmountedRef.current=true;};},[]);returnunmountedRef;};useGetState给React.useState增加了一个getter方法,以获取当前最新值。
其实现如下:
其实就是通过useRef记录最新的state的值,并暴露一个getState方法获取到最新的。
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));0这在某一些情况下,可以避免React的闭包陷阱。如官网例子:
setObj((prev)=>({ name:'Gopal',others:{ age:'',}}));1假如这里不使用getCount(),而是直接使用count,是获取不到最新的值的。
总结与思考React的functionComponent的状态管理还是比较灵活,我们可以针对一些场景进行封装和优化,从而更优雅的管理我们的state状态,希望ahooks这些封装能对你有所帮助。
原文:/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 中,所有操作默认为异步批量执行,将消除同步异步的讨论。