setState是同步还是异步
回想在使用react的setState时,setState操作完成之后,可以立马拿到结果吗?
在合成事件、react的生命周期函数是不可以立马拿到结果的,😯,那setState是异步的;
不对,但在setTimeOut和原生事件中,是可以立马拿到结果的,setState是同步的;
😊,那setState到底是同步的还是异步的呢?其实setState有时的表现是同步,有时的表现是异步
React15中setState具体实现流程
- enququUpdate部分源码
function enqueueUpdate(component) {
// 如果没有处于批量创建/更新组件的阶段,则处理update state事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
dirtyComponents.push(component);
}
同步与异步的区别是通过改变isBatchingUpdates的布尔值来实现的;
React16中setState具体实现,结合具体场景
setState异步的场景有哪些
合成事件中的setState
合成事件 — React为了解决事件的跨平台性和兼容性,对原生的事件机制进行了封装,形成了合成事件,比如下面一段代码
class App extends React.Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
上面合成事件中,源码执行的流程
function requestWork(root, expirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpiration(expirationTime);
}
}
由代码可知,合成事件后的会进入第二个分支,会直接return;最终回到了interactiveUpdates这个方法,这个方法中有个try finally语法,会先执行try代码块中的语句,再执行finally中的代码;
function interactiveUpdates$1(fn, a, b) {
if (isBatchingInteractiveUpdates) {
return fn(a, b);
}
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
if (!isBatchingUpdates && !isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
// Synchronously flush pending interactive updates.
performWork(lowestPendingInteractiveExpirationTime, false, null);
lowestPendingInteractiveExpirationTime = NoWork;
}
var previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
var previousIsBatchingUpdates = isBatchingUpdates;
isBatchingInteractiveUpdates = true;
isBatchingUpdates = true; // 把requestWork中的isBatchingUpdates标识改为true
try {
return fn(a, b);
} finally {
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
所以当在increment中调用setState之后去console.log时,是属于try代码块中执行,并没有完成state的更新,所以输入的结果还是原来的state值;当在try代码块执行结束,即increment函数执行结束,就会执行finally中的代码,执行了performSyncWork,这时候会更新state并且渲染到UI上;
React生命周期函数中的setState
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
在生命周期函数componentDidMount中,isRending=true,会直接return,所以并不会立马更新,会有异步的效果;
setState同步的场景有哪些
原生事件中的setState
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
原生事件中,没有react合成事件的执行流程,在执行到requestWork时,因为 expirationzTime === Syn 的条件,会直接执行performSyncWork去更新,所以能够立马输出结果,是同步的;
setTimeOut中的setState
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
所有setTimeOut中的setState都可以同步的拿到值,因为setTimeOut属于宏任务;比如之前的合成事件,由于你是 setTimeout(_ => { this.setState()}, 0) 是在 try 代码块中,当你 try 代码块执行到 setTimeout 的时候,把它丢到列队里,并没有去执行,而是先执行的 finally 代码块,等 finally 执行完了, isBatchingUpdates 又变为了 false ,导致最后去执行队列里的 setState 时候, requestWork 走的是和原生事件一样的 expirationTime === Sync if分支,所以表现就会和原生事件一样,可以同步拿到最新的state值。
总结
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
😔第一篇博客,感觉还是参考的很多,慢慢学习吧!