React 篇之浅谈 setState is异步OR同步
react 篇主要是记录笔者之前在使用 React 进行开发时遇到的问题和坑, 趁还没有毕业 一 一 查阅文档资料总结归纳, 和大家一起分享, 以防重蹈覆辙.
这篇文章主要总结的是关于利用 setState
更改组件状态时遇到的一些坑, 希望会的小伙伴可以当做复习巩固,不会的可以当做学习.
setState 究竟是异步的还是同步的,为何有的时候是同步,有的时候异步呢?
setState 异步更新
案例
class Example extends React.Component{
constructor(){
super(...arguments)
this.state = {
count: 0
};
}
componentDidMount(){
//第一次更新状态
this.setState({count: this.state.count + 1});
console.log(this.state.count)
//第二次更新状态
this.setState({count: this.state.count + 1});
console.log(this.state.count)
setTimeout(() => console.log(this.state.count),0)
}
}
运行结果依次是:
0 0 1
这就有点纳闷了, 打印出来的不应该是 1 2 2
吗?
其实这里的状态更新是'异步'的, setState
是通过一个队列机制来实现state
更新的, 当执行setState()
时, 就会将需要更新的 state
合并 (浅合并!!) 后在放入状态队列中, 而不是立即更新 state
, react 中的状态队列机制
可以起到高效批量更新state
由此可以知道 React 的 setState
是通过状态队列机制实现的, 以此避免了重复的更新的 state
另外在上文提到的setState会将需要更新的state合并
,这是怎么回事呢?
setState(nextState[,callback])
React 官方文档对 setState
有明确的介绍到:
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state
举个例子:
this.setState({count: state.count + 1})
this.setState({count: state.count + 2})
this.setState({count: state.count + 3})
//结果的state.count 值为 0
当你同时对同一个或多个几个状态进行更新时,就等同于
this.setState(Object.assign(state,
{count: state.count + 1},
{count: state.count + 2},
{count: state.count + 3}
))
那么, 如果在开发中, 迫不得已需要对一个状态多次更新,但又要保证这个状态是可靠的,没方法了吗?
别急, 你可以想到的官方也有想到过, 要解决这个问题, 可以让 setState()
接收一个函数而不是一个对象. 这个函数用上一个 state
作为第一个参数, 将此次更新被应用时的 props
做为第二个参数.
使用方法:
//假设 this.props = {addVal: 1}
this.setState((preState,props) => ({
count: preState.count + props.addVal
}))
此时的setState()
有点像数组的renduce
(累加器)
//假设 this.props = {addVal: 1}
Array(upDateCount)
.fill(this.props)
.reduce((preState,props) => ({
count: preState + props.addVal
}),{count: 0})
// 最终 {count: 3}
setState 同步更新
案例
class Example2 extends React.Component{
constructor(){
super(...arguments)
this.state = {
count: 0
};
}
componentDidMount(){
setTimeout(() => {
//第一次更新状态
this.setState({count: this.state.count + 1});
console.log(this.state.count)
//第二次更新状态
this.setState({count: this.state.count + 1});
console.log(this.state.count)
},0)
}
}
运行结果依次是:
1 2
既然说 setState
是异步的, 那么结果应该是0 0
,为何可以正常捕获到状态更新完的值呢, 然而setState
不尽然在任何地方都是异步
的, setTimeout 中调用以及原生事件中调用的话, 是可以立马获取到最新的 state 的.
其实在 React 中, 如果是由 React 引发的事件处理即合成事件
(比如通过onClick
引发的事件处理) 或者钩子函数(如生命周期函数等绑定事件函数), 调用setState
不会同步更新this.state
, 除此之外的setState
调用会同步执行this.state
, 所谓'除此之外',指的是绕过 React 通过addEventListener
(原生事件)直接添加的事件处理函数, 还有通过setTimeout/setInterval
(定时器)产生的异步调用。
那么 React 究竟何时处于异步,何时同步呢?
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdate 来判断是直接同步更新 this.state 还是放到队列中异步更新 。React 使用了事务的机制,React 的每个生命周期和合成事件都处在一个大的事务当中。在事务的前置钩子中调用 batchedUpdates 方法修改 isBatchingUpdates 变量为 true,在后置钩子中将变量置为 false。原生绑定事件和 setTimeout 异步的函数没有进入到 React 的事务当中,或者当他们执行时,刚刚的事务已近结束了,后置钩子触发了,所以此时的 setState 会直接进入非批量更新模式,表现在我们看来成为了同步 SetState。
关于 React 中的setState
何时属于异步,何时异步点到为止, 笔者目前能力有限(学不过来),只能在自己的理解范围内讨论, 只能在今后的码路上继续前进,进一步深入了解