在react管控下的setState是异步的,但在setTimeout下的setState是同步。
class组件的setState工作流:
首先setState的入口函数相当于一个分发器,根据入参的不同,会将其分发的不同功能函数中,然后就会继续调用enqueueSetState 这个方法,这个方法主要做了两件事
- 将新的 state 放进组件的状态队列里;
- 用 enqueueUpdate 来处理将要更新的实例对象。
enqueueUpdate中通过batchingStrategy对象中的isBatchingUpdates属性去判断当前是否处于批量处理的阶段,如果不是,那就立即更新组件,否则就把组件塞入dirtyComponents 队列里
我们再来看setState为什么出现同步问题?
这是因为 isBatchingUpdates 这个方法,不仅仅会在 setState 之后才被调用。
-
在首次渲染组件的时候,也会调用一次 isBatchingUpdates
这是因为在组件的渲染过程中,会按照顺序调用各个生命周期函数,开发者很有可能在声明周期函数中调用 setState,因此,我们需要通过开启 batch 来确保所有的更新都能够进入 dirtyComponents 里去,进而确保初始渲染流程中所有的 setState 都是生效的。
-
绑定事件的时候,也会调动isBatchingUpdates
// 进来先锁上
isBatchingUpdates = true
console.log('increment setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
console.log('increment setState后的count', this.state.count)
// 执行完函数再放开
isBatchingUpdates = false
//如果放到setTimeout中
// 进来先锁上
isBatchingUpdates = true
setTimeout(() => {
console.log('reduce setState前的count', this.state.count)
this.setState({
count: this.state.count - 1
});
console.log('reduce setState后的count', this.state.count)
},0);
// 执行完函数再放开
isBatchingUpdates = false
所以我们会发现isBatchingUpdates对于setTimeout完全没有约束力,因为isBatchingUpdates是同步代码,在setState发生调用的时候,isBatchingUpdates已经被置为false了。
如何在如上异步环境下,继续开启批量更新模式呢?
React-Dom 中提供了批量更新方法 unstable_batchedUpdates,可以去手动批量更新
setTimeout(()=>{
unstable_batchedUpdates(()=>{
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
this.setState({ number:this.state.number + 1})
console.log(this.state.number)
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
})
})
那么如何提升更新优先级呢?
React-dom 提供了 flushSync ,flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中。React 设定了很多不同优先级的更新任务。如果一次更新任务在 flushSync 回调函数内部,那么将获得一个较高优先级的更新。
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
// 3 4 1
类组件中的 setState 和函数组件中的 useState 有什么异同?
- 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。
不同点
- 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
- setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
- setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。