一、说明
关于调用 setState()
进行状态更新,官方有如下说明
State Updates May Be Asynchronous React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. 译: State 的更新可能是异步的 出于性能方面的考虑,React 可以将多次的 setState() 调用合并为一次 因为 this.props 和 this.state 可能是异步更新的,你不应该用它们当前的值去计算下一个 state 的值
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。
二、setState何时同步何时异步?
由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。
React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。
三、同步更新策略
1.完成回调
setState函数的第二个参数允许传入回调函数,在状态更新完毕后进行调用,譬如:
this.setState({ load: !this.state.load, count: this.state.count + 1 }, () => { console.log(this.state.count); console.log('加载完成') });
2.Promise封装结合async/await
其实这里就是JavaScript异步编程相关知识,将上面回调写法换成Promise
引入Promise来封装setState:
setStateAsync(state) { return new Promise((resolve) => { this.setState(state, resolve) }); }
setStateAsync 返回的是Promise对象,在调用时我们可以使用Async/Await语法来优化代码风格:
async componentDidMount() {
}
3.直接使用操作异步函数async / await
此时每次await setState都会执行一遍render方法
handleChange = async () => {
}
四、setState参数详解
state中定义count初始值为1;
1.第一个参数为传入对象;
handleChange = () => { this.setState({ count: this.state.count + 2 }) console.log(123, this.state.count) this.setState({ count: this.state.count + 2 }) console.log(456, this.state.count) }
调用方法两次输出值为都为3;
React 出于性能方面的考虑,并不会直接对每次的调用都进行更新,而是会将多次传入的对象进行合并处理,以产生一个新的最终的 state
对象;
第二个参数为完成回调
handleChange = () => { this.setState({ count: this.state.count + 2 }, () => { console.log(123, this.state.count) }) this.setState({ count: this.state.count + 2 }, () => { console.log(456, this.state.count) }) }
与上面输出保持一致
2.第一个参数为传入函数:
handleChange = () => { this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(123, this.state.count) }) this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(456, this.state.count) }) }
函数第一个参数接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数;
此时输出为两次都是5;
在第二次调用 setState 方法时便可以通过 prevState.count 拿到最新的值从而更新本次的 state 。显然,React 对于传入函数的方式和传入对象的方式进行更新 state 的各自具体理念是不一样的,对于传入函数的方式,在调用 setState 进行更新 state 时,React 会按照各个 setState 的调用顺序,将它们依次放入一个队列,然后,在进行状态更新时,则按照队列中的先后顺序依次调用,并将上一个调用结束时产生的 state 传入到下一个调用的函数中,当然,第一个 setState 调用时,传入的 prevState 则是当前的 state ,如此,便解决了传入对象式调用 setState 方法所存在的 不能依赖上一次的 state 去计算本次 state 的问题。
handleChange = () => { this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(123, this.state.count) }) this.setState((prevState, props) => ({ count: this.state.count + 2 }), () => { console.log(456, this.state.count) }) }
值得一提的是,在上面的这段代码中,执行第二个 setState 里面的函数时,由第一个 setState 所产生的最新的 state 并没有合并到 this 对象上面去,所以此时通过 this.state 获取不到最新的状态,故而在函数内部拿到的 this.state.count 的值为 1 而非 3。
另外,不论第一个参数是何种类型,多次setState也只会调用一次render方法,第二个参数callback完成回调中获取this.state的值同样也是多次setState合并操作后的最终值;