setState的更新是异步的?
-
最终打印结果是Hello World;
-
可见setState是异步的操作,我们并不能在执行完setState之后里面拿到最新的state的结果
-
异步执行结果如下图

-
1.3.3. 为什么setState设计为异步呢?
- setState设计为异步其实之前在GitHub上也有很多的讨论;
- React核心成员(Redux的作者)Dan Abramov也有对应的回复,有兴趣的可以参考下一下链接
- 官网地址:https://github.com/facebook/react/issues/11527#issuecomment-360199710
-
1.3.4. 我对其回答做一个简单的总结:
*- setState设计为异步,可以显著的提升性能;
-
1.1. 如果每次调用setState都进行一次更新,那么以为意味着render函数会被频繁调用,界面重选渲染那,这样效率很低的;
-
1.2. 最好的办法应该是获取到多个更新,之后进行批量更新;
-
- 如果同步更新了state, 但是还没有执行render函数,那么state和props不能保持同步;
- 2.1. state和props不能保持一致性,会在开发中产生很多的问题;
-
- setState批量更新机制:
-
3.1.
React18之前是同步的,React18之后都是异步的 -
3.2. 这里
调用了setState三次,render函数就会执行三次,进行三次diff算法,dom会更新三次,dom更新的话会引起回流和重绘 -
3.3. 为什么用调用setState三次?
- 理想情况下可以直接调用counter + 3
- 但是在真实开发中会出现componentDidMount里面同时发送网络请求然后差不多时间段返回数据让setState执行多次进行赋值,但是更新state只要更新一次就可以了,更新只要更新一次就可以了
-
3.4 所以最好的办法:获取到多个更新,之后进行批量更新(一起做一个合并,合并完值之后调用一次render函数就可以,对DOM也是进行一次diff算法,对DOM进行一次更新)
-
3.5 React18之前有一些地方是不会做批量处理的,例如:promise的then回调不会做批处理,React18之后所有东西都是批量处理
-
3.6. 关键代码代码如下:
jsthis.setState({ counter: this.state.counter + 1 // 0 + 1 }) this.setState({ counter: this.state.counter + 1 // 0 + 1 }) this.setState({ counter: this.state.counter + 1 // 0 + 1 })
-
- setState异步更新的原理:
- 原理:当执setState更新值操作的时候,会做一个批量处理,把要更新的东西做一个异步操作,先不更新。搞一个队列,会把更新的操作加入到队列里面,等到需要真正更新的时候,取出每次需要更新具体的内容,进行依次合并,队列是有顺序的,队列的特点就是先进先出;
- 内部源码:内部做了一个
do{} while循环,用do{} while循环把队列里面所有东西都给取出来,取出来之后挨个合并,合并完之后在执行一次render函数
-
- 上面的代码就会被合并,只会执行一次,真的会被合并吗?
- 这样来的写的话
this.state.counter + 1,this.state.counter取到的是一个值为0, 所以是:0 + 1,依然是有合并的,在构建对象的时候,就已经把counter的值取出来了,所以是0+1为1,所以相当于把下面的代码三步做了一样的操作0+1,这里还没涉及设置它 - 有点类似于obj1的操作
const obj1 = { counter: 0 + 1 } - 但是结果是为1的,每次设置值,都是0+1这个值,这个东西同时依然有合并,可以验证它的合并过程,值为1, render函数只执行了一次
- 如下图:

- 关键代码如下:
jsexport class App extends Component { constructor() { super() this.state ={ message: 'hello world', counter: 0 } } increment () { console.log('-----------') this.setState({ counter: this.state.counter + 1 // 0 + 1 }) this.setState({ counter: this.state.counter + 1 // 0 + 1 }) this.setState({ counter: this.state.counter + 1 // 0 + 1 }) } render() { const { message, counter } = this.state console.log('render被执行') return ( <div> <h2>message: {message}</h2> <button onClick = {e => this.changeText()}>修改文本</button> <h2>当前计数:{ counter }</h2> <button onClick= { e => this.increment()}>+1</button> <Hello message={message} /> </div> ) } } -
- 其实上面这段代码刚好可以验证是异步的,如果是同步的话,第一个setState更新完之后结果应该是1,第二个setState更新完之后结果应该是2, 第三个setState更新完之后结果应该是3
但是它没有,结果依旧是1,所以间接证明了它就是异步的,render函数执行一次
- 其实上面这段代码刚好可以验证是异步的,如果是同步的话,第一个setState更新完之后结果应该是1,第二个setState更新完之后结果应该是2, 第三个setState更新完之后结果应该是3
-
- 现在合并的值依然是之前的值,如果
想要真正的被合并,传入一个回调函数
- 运行结果如下图:

- 关键代码如下:
jsx// 现在合并的值依然是之前的值,如果想要真正的被合并,传入一个回调函数 // 这里主要讨论了两个问题: // 一:就是在一个点击函数里面执行了三次setState, render函数也只会被执行一次 // 二:三次里面刚好出现了递增的情况, 需要传入回调函数,在回调函数里面使用传入进来的state, 因为传入进来的state已经是合并完之后另外一个state的值(更新后的值) this.setState((state) =>{ // 使用传递进来state // 这个state里面的数据更新 这个本质上是放到一个队列,队列里面放了一个个函数,到时候取出来的时候会回调这个函数,回调这个函数的时候会传入一个state,回调这个函数的时候会把最新的state传进来 // 经过函数的调用的话就会做一个+1的操作,紧接着取出来第二个函数的时候,又要传入一个state,原来的state就已经+1了,所以传进来的state就是另外一个state(更新计算后的state值), 拿到最新的state进行回调 // 这里拿到的this.state的值是不是正确的? // 回调这个函数的时候准备做更新了, 不是加入到队列,已经从队列里面取出来了准备开始执行了 ,这个地方this.state的值有没有更新取决于上次更新有没有把上一次this.state里的值有没有改掉还是一次性改掉 // 取到的值依然是0,那就是依然没有更改,做一次性更改,等到所有东西都合并到一起之后再做一次性更新 // console.log(this.state.counter) return { counter: state.counter + 1 } }) this.setState((state) =>{ return { counter: state.counter + 1 } }) this.setState((state) =>{ return { counter: state.counter + 1 } }) - 现在合并的值依然是之前的值,如果
-
3./4./5./6./7.主要讨论了两个问题:
-
- 问题一:就是在一个点击函数里面执行了三次setState,
render函数也只会被执行一次
- 问题一:就是在一个点击函数里面执行了三次setState,
-
- 问题一:
调用三次setState里面刚好出现了递增的情况, 需要传入回调函数,在回调函数里面使用传入进来的state, 因为传入进来的state已经是合并完之后另外一个state的值(更新后的值)
- 问题一:
1.4. 如何获取异步的结果
-
- 那么如何可以获取更新后的值呢?
-
- 方式一: setState的回调
- 2.1. setState接受两个参数,第二个参数是一个回调函数,这个回调函数会在更新后执行;
- 2.2. 格式如下:
setState(partialState, callback)

-
- 方式二: 也可以在生命周期函数:
jscomponentDidUpdate(prevProps, prevState, snapshot) { console.log(this.state.message); }
1.5. setState一定是异步吗?(React18之前)
-
- 验证一:setTimeout中的更新
jssetTimeout(() => { // 在React18之前,setTimeout中setState操作,是同步操作 // 在React18之后,setTimeout中setState是异步操作(批处理) this.setState({ message: '你好啊,李银河' }) console.log(this.state.message) // React18之前`你好啊,李银河` React18之后`hello World` }, 0) -
- 验证二:原生DOM事件
jscomponentDidMount() { const btnEl = document.getElementById('btn'); btnEl.addEventListener('click', () => { this.setState({ message: '你好啊,李银河' }) // React18之前`你好啊,李银河` }) }-
- 其实分层两种情况:
-
- 在组件生命周期或React合成事件(React事件回调), setState是异步;
-
- 在setTimeou或者原生dom事件中,setState是同步;
1.6. setState一定是异步吗?(React18之后)
-
- 在React18之后,默认所有的操作都被放到了批处理中(异步处理)
- 官网截图:

-
- 如果希望代码可以同步拿到,则需要执行特殊的flushSync操作;
js
// 导入flushSync
import { flushSync } from 'react-dom'
// 使用flushSync,传入一个回调函数
flushSync(() => {
this.setState({ message: '你好啊,李银河' })
// 这里的代码依然是批处理,这个回调里面的函数里的代码都是当成一次更新
console.log('flushSync====',this.state.message) // hello world
})
console.log(this.state.message) // `你好啊,李银河`
