React官网介绍useState的setter函数时这样说的:
测试伪代码如下:
function handleClick() {
console.log('start')
setCount(count => {
console.log('click');
return count + 1;
})
console.log('end')
}
按照官方理解:触发handleClick时,打印start,然后把setCount里的回调函数放入队列(并不执行),打印end,事件处理函数(handleClick)代码都运行完毕了,再执行队列里的函数,
所以理论打印顺序为start,end,click
可实际打印顺序是:start,click,end
官网也有例子,就是把setCount里的函数都放在队列里,并不单单是只放return的值,所以说log语句也放入队列了。
为什么打印顺序和理解的不一样?
回答:
针对问题中提到的setState问题,可以简单地总结如下:
setState是异步的,但是React在一些情况下(比如在React事件处理函数中)会启用批处理机制对setState进行优化,从而实现同步更新的效果。
如果多次在同一个事件处理函数中调用setState,React会将这些调用合并成一次操作,只会执行一次state更新,因此通过多次输出state的值,不能证明setState是同步更新的。
具体地,我们可以通过以下方式进行验证:
针对useState的情况,官网中介绍的“React会等到事件处理函数中的所有代码都运行完毕再处理state更新”可以看作是一个理论描述。在实际情况中,由于React支持批处理机制,因此在一些情况下,会实现同步更新的效果。具体来说,React在一些事件处理函数中(如setTimeout、Promise.then等)启用批处理机制,将setState的调用放置到一个队列中,等待当前事件处理函数执行完毕后再进行处理。因此在这些情况下,通过多次输出state的值,可能会发现setState实际上是同步更新的。但是需要注意的是,React并不会对所有事件处理函数都启用批处理机制,因此一般情况下,依然可以认为setState是异步的。
针对React类组件中的setState,与useState类似,同样存在异步更新的情况。具体来说,如果多次在同一个事件处理函数中调用setState,React会将这些调用合并成一次操作,只会执行一次state更新。因此通过多次输出state的值,不能证明setState是同步更新的。以下代码可以作为一个示例来进行验证:
import React, { Component } from 'react';
export default class Example extends Component {
state = {
val: 0
};
componentDidMount() {
this.setState({val: this.state.val + 1}); // 1
console.log(this.state.val); // 0
this.setState({val: this.state.val + 1}); // 2
console.log(this.state.val); // 0
setTimeout(() => {
this.setState({val: this.state.val + 1}); // 3
console.log(this.state.val); // 2
this.setState({val: this.state.val + 1}); // 4
console.log(this.state.val); // 3
}, 0);
}
render() {
return null;
}
}
在该代码中,通过多次输出state的值,我们可以发现,setState的更新实际上是异步的,而且React会将在同一个事件处理函数中的多次调用进行合并,只会执行一次state更新。同时,在setTimeout中启用了批处理机制,因此setState的更新也会进行同步处理。
综上所述,React中的setState更新并非一成不变的,它既可以是异步更新,也可以是同步更新,具体取决于所处的环境。因此,在编写代码时,需要注意一些细节,防止因为setState更新的行为不符合预期而出现一些问题。