我们先来思考一个老生常谈的问题,setState是同步的还是异步的?再仔细想想,useState是同步的还是异步的?我们写几个demo试试看。先看useState同步和异步情况,连续执行两个useState实例functionComponent(){const[a,setA]=useState(1)const[b,setB]=useState('b')console.log('render')functionhandleClickWithPromise(){Promise.resolve().then(()=>{setA((a)=>a+1)setB('bb')})}functionhandleClickWithoutPromise(){setA((a)=>a+1)setB('bb')}return({a}-{b}异步执行{a}-{b}同步执行)}结论:点击同步执行按钮时,只重新渲染一次。单击异步执行按钮时,将执行两次渲染。同样的useState例子functionComponent(){const[a,setA]=useState(1)console.log('a',a)functionhandleClickWithPromise(){Promise.resolve().then(()=>{setA((a)=>a+1)setA((a)=>a+1)})}functionhandleClickWithoutPromise(){setA((a)=>a+1)setA((a)=>a+1)}return({a}异步执行{a}同步执行)}当点击同步执行按钮时,setA执行了两次,但是render合并了一次,打印了3。同步和异步情况下,连续执行两个setStateExampleclassComponentextendsReact.Component{constructor(props){super(props)this.state={a:1,b:'b',}}handleClickWithPromise=()=>{Promise.resolve().then(()=>{this.setState({...this.state,a:'aa'})this.setState({...this.state,b:'bb'})})}handleClickWithoutPromise=()=>{this.setState({...this.state,a:'aa'})this.setState({...this.state,b:'bb'})}render(){console.log('render')return(异步执行同步执行)}}当点击同步执行按钮时,只重新渲染一次当点击异步执行按钮时,渲染两次参考前端高级面试题详细答案同useState的结果。在同步和异步情况下,执行两次相同的setState。示例classComponentextendsReact.Component{constructor(props){super(props)this.state={a:1,}}handleClickWithPromise=()=>{Promise.resolve().then(()=>{this.setState({a:this.state.a+1})this.setState({a:this.state.a+1})})}handleClickWithoutPromise=()=>{this.setState({a:this.state.a+1})this.setState({a:this.state.a+1})}render(){console.log('a',this.state.a)return(异步执行同步执行)}}当点击同步执行按钮时,这两个setStates被合并,只执行最后一个,打印2。当点击异步执行按钮时,这两个setStates分别渲染一次,我们这里打印了2和3eState则不同,useState在同步执行的时候也会对states进行一个一个的处理,而setState只会处理最后一次。为什么同步执行和异步执行会有不同的结果?这就涉及到了react的batchUpdate机制。首先,为什么需要合并更新?如果没有mergeupdate,每次执行useState都会重新渲染组件,会造成渲染无效,浪费时间(因为最后一次渲染会覆盖之前的所有渲染效果)。所以react会把一些可以一起更新的useState/setState放在一起进行mergedupdate。React如何合并和更新这里使用了事务机制。React中的批量更新是通过“事务”实现的。在React源码中关于Transaction的部分,用大段文字和人物画来解释Transaction的作用:*wrappers(injectedatcreationtime)*++*||*+------------------|--------|------------+*|v||*|+-----------------+||*|+--|包装器1|---|----+|*||+----------------+v||*||+------------+||*||+----|wrapper2|--------+|*|||+-------------+|||*||||||*|vvvv|包装纸*|+---++---++--------++---++---+|不变量*执行(任何方法d)||||||||||||维护*+---------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->*||||||||||||*||||||||||||*||||||||||||*|+---++---++--------++---++---+|*|初始化关闭|*+----------------------------------------+通俗易懂,在实际的useState/setState前后加了一段逻辑将其包裹起来,只要是同一个事务中的setState就会合并(注意useState不会合并状态)处理。为什么setTimeout不能进行事务性操作由于react的事件委托机制,调用onClick执行的事件在react的控制范围内。但是setTimeout是react无法控制的,react不能在setTimeout的代码中加入业务逻辑(除非react重写setTimeout)。所以遇到setTimeout/setInterval/Promise.then(fn)/fetchcallback/xhr网络回调时,react是不可控的。相关的React源代码如下:if(executionContext===NoContext){//Flushthesynchronousworknow,unlesswe'realreadyworkingorinside//abatch.这是故意在scheduleUpdateOnFiber而不是//scheduleCallbackForFiber中以保留安排回调的能力//而不会立即刷新它。我们只对用户启动的//更新执行此操作,以保留遗留模式的历史行为。flushSyncCallbackQueue()}executionContext代表的是react的当前阶段,NoContext你可以理解为是react没有工作可做的状态。而flushSyncCallbackQueue会同步调用我们的this.setState,也就是说我们的state会同步更新。所以,我们知道,当executionContext为NoContext时,我们的setState是一个同步汇总。总结一下上面实验的结果:在正常的react事件流中(比如onClick等)setState和useState是异步执行的(state的结果不会立即更新)多次执行setState和useState只会调用重新渲染一次。不同的是setState会合并state,而useState不会在setTimeout和Promise等异步事件中setState和useState。然后useState同步执行(立即更新state的结果)多次执行setState和useState,每setState和useState执行时,render会被调用一次。是不是感觉有点乱?自己写代码体验一下吧~