React中的setState是异步的吗?
setState
是同步执行的!但是state并不一定会同步更新(异步更新和同步更新都存在)
setState()
中有个特别重要的布尔属性isBatchingUpdates
(默认为false),它决定了state
是同步更新还是异步更新。
异步更新:
- 合成事件
- 钩子函数
setState
只在合成事件和钩子函数中是“异步更新”的。异步更新的背后,是同步代码处理("合成事件和钩子函数"的调用在"更新"之前)。异步是为了实现批量更新的手段,也是React性能优化的一种方式。
同步更新:
setTimeout
等异步操作中调用setState函数- DOM原生事件
- 利用
setState
回调函数- 函数式
setState
用法前两个都比较好理解,因为没有前置的
batchedUpdate
调用,所以isBatchingUpdates
为false。不会开启批量更新模式,那么,在上面的调用栈图示里面,会直接走到事务更新。后面两个方法,是React本身提供的。要注意的是,
setState
回调函数要在render函数被重新执行后才执行。
为什么有时连续两次
setState
只有一次生效?分别执行以下代码:
componentDidMount() { this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) }
执行结果:
1 1
componentDidMount() { this.setState((preState) => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) this.setState(preState => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) }
执行结果
2 2
说明:
- 1.直接传递对象的
setstate
会被合并成一次- 2.使用函数传递
state
不会被合并
测试输出
import React from "react"; class TestView extends React.Component { state = { count: 0, }; componentDidMount() { this.setState({ count: this.state.count + 1, }); console.log("console: " + this.state.count); this.setState({ count: this.state.count + 1 }, () => { console.log("console from callback: " + this.state.count); }); this.setState( (prevState) => { console.log("console from func: " + prevState.count); return { count: prevState.count + 1, }; }, () => { console.log("last console: " + this.state.count); } ); } render() { console.log("render" + this.state.count); return <h4>test</h4>; } } export default TestView;
输出结果:
// console: 0 // console from func: 1 // console from callback: 2 // last console: 2
分析:
由于第一个和第二个setState是直接传递对象的,所以两次操作被合并成一次,然后由于这两次操作state是异步更新,此时的count还是0,输出 console:0
由于第二个setState是异步的,所以其回调也推入异步队列中。
执行第三个setState,此处为函数式调用setState,是同步更新的,此时取得prevState的count为上一次更新的count值1,此时输出 console from func: 1,并且执行count值+1变为2
第三个setState执行完毕,将其回调函数推入异步更新队列。
同步更新完毕,执行异步更新队列,先进先出,输出 console from callback: 2 ,最后 输出 last console: 2
总结
- 钩子函数和合成事件中:
在 react
的生命周期和合成事件中, react
仍然处于他的更新机制中,这时 isBranchUpdate
为true。
按照上述过程,这时无论调用多少次 setState
,都会不会执行更新,而是将要更新的 state
存入 _pendingStateQueue
,将要更新的组件存入 dirtyComponent
。
当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件 didmount
后会将 isBranchUpdate
设置为false。这时将执行之前累积的 setState
。
- 异步函数和原生事件中
由执行机制看, setState
本身并不是异步的,而是如果在调用 setState
时,如果 react
正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。
在生命周期,根据JS的异步机制,会将异步函数先暂存,等所有同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕, isBranchUpdate
被设置为false,根据上面的流程,这时再调用 setState
即可立即执行更新,拿到更新结果。
参考文章: