-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 中 setState() 为什么是异步的 #12
Comments
最后一段,运行结果还是 0 0 2 3,并不是 0 1 3 4 |
楼上说的对 , 运行结果还是 0 0 2 3,并不是 0 1 3 4, 楼主大佬解释下呗 |
改成这样就对了
|
因为第二个setState里this.state的数据还不是最新的,此时this.state.val还是0。而参数state表示上一次更新后的state,是最新的,state.val是1。 可以参考这篇blog:https://blog.csdn.net/Mr_28/article/details/84778001 |
@DangoSky 优秀 |
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
.....
} 对于上面一段代码为什么会是0,0 ,笔者将源码缩略后,原理如下: class Component {
constructor() {
this.updateQueue = [];
this.isBatchingUpdates = false; // 是否需要暂存
this.state = {
val: 0
};
}
setState(newState) {
if(this.isBatchingUpdates) {
this.updateQueue.push(newState)
}
}
add() {
this.isBatchingUpdates = true
this.setState({number: this.state.val + 1}) // this.state.val = 0
this.setState({number: this.state.val + 1}) // this.state.val = 0
this.setState({number: this.state.val + 1}) // this.state.val = 0
this.flushQueue()
}
flushQueue() {
this.updateQueue.map(newState => this.state = newState)
this.isBatchingUpdates = false;
}
}
let c = new Component()
c.add() |
this.setState((state) => {
console.log(state.val) // 0
return { val: state.val + 1 }
})
this.setState((state) => {
console.log(state.val) // 1
return { val: state.val + 1 }
}) 上面的代码之所以可以说实现0,1 效果,其原理如下: class Component {
constructor() {
this.updateQueue = [];
this.callbackQueue = [];
this.isBatchingUpdates = false;
this.state = {
val: 0
};
}
setState(partialState, callback) {
if(this.isBatchingUpdates) {
this.updateQueue.push(partialState)
this.callbackQueue.push(callback)
}
}
add() {
this.isBatchingUpdates = true
this.setState((preState) => ({val: preState.val + 1}), () => {
console.log(this.state.val)
})
this.setState((preState) => ({val: preState.val + 1}), () => {
console.log(this.state.val)
})
this.setState((preState) => ({val: preState.val + 1}), () => {
console.log(this.state.val)
})
this.flushQueue()
}
flushQueue() {
let partialState = this.updateQueue.reduce((pre, next) => {
return next(pre)
}, this.state)
this.state = {...this.state, ...partialState}
this.callbackQueue.map(callback => callback())
this.isBatchingUpdates = false;
}
}
let c = new Component()
c.add() |
Dan 老哥的回答
1. 保证内部的一致性
即使state是同步更新,props也不是。(你只有在父组件重新渲染时才能知道props)
2. 性能优化
将state的更新延缓到最后批量合并再去渲染对于应用的性能优化是有极大好处的,如果每次的状态改变都去重新渲染真实dom,那么它将带来巨大的性能消耗。
原理解释
为什么这么说。可以通过下面的例子
不是真正意义上的异步操作
探讨前,我们先简单了解下react的事件机制:react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的
onClick
、onChange
这些都是合成事件。那么以上4种方式调用setState(),后面紧接着去取最新的state,按之前讲的异步原理,应该是取不到的。然而,
setTimeout
中调用以及原生事件中调用的话,是可以立马获取到最新的state的。根本原因在于,setState并不是真正意义上的异步操作,它只是模拟了异步的行为。React中会去维护一个标识(isBatchingUpdates
),判断是直接更新还是先暂存state进队列。setTimeout
以及原生事件都会直接去更新state,因此可以立即得到最新state。而合成事件和React生命周期函数中,是受React控制的,其会将isBatchingUpdates
设置为 true,从而走的是类似异步的那一套。在 setTimeout 中去 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout ,可以在钩子函数中 setTimeout ,也可以在原生事件setTimeout,但是不管是哪个场景下,基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值。
源码分析
总结
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的。setState
的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。批量更新
优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout
中不会批量更新,在“异步”中如果对同一个值进行多次 setState
, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。对于3 可以结合下面的例子:
结合上面分析的,钩子函数中的 setState 无法立马拿到更新后的值,所以前两次都是输出0,当执行到 setTimeout 里的时候,前面两个state的值已经被更新,由于 setState 批量更新的策略, this.state.val 只对最后一次的生效,为1,而在 setTimmout 中 setState 是可以同步拿到更新结果,所以 setTimeout 中的两次输出2,3,最终结果就为 0, 0, 2, 3 。
想一下,如何将上面的代码中前两次连续 + 1 都执行呢?
直接赋值原理
返回函数原理
The text was updated successfully, but these errors were encountered: