很多人在面试的时候遇到过这样一道题,
state = {
count: 0,
};
handleClick = () => {
this.count();
};
count = () => {
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
};
render() {
return (
<div>
<button onClick={this.handleClick}>{this.state.count}</button>
</div>
);
}
问:这几个console分别是什么?
很多人都会说setState会合并,所以三个都是0。可能再加个异步的说法。然而我们再看下面这道题。
state = {
count: 0,
};
handleClick = () => {
setTimeout(() => {
this.count();
},0)
};
count = () => {
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
};
render() {
return (
<div>
<button onClick={this.handleClick}>{this.state.count}</button>
</div>
);
}
在this.count
外面包了一层setTimeout
,那么现在的结果是什么呢?
很出乎意料,加了setTimeout
之后结果就变成了
好像之前的异步合并都不存在了,这是为什么呢。
function requestWork(root: FiberRoot, expirationTime: 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, true);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
react里的更新都要经过requestWork,执行setState的时候通过debug可以看到三次setState过程isBatchingUpdates
都为true,也就是都会return,并不会执行react的调度更新performSyncWork
。那为什么会这样呢,我们往前看执行过程,可以找到一个batchedUpdates
的方法。
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a);
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
可以看到,它会return执行一个函数,这个函数相当于我们上面的handleClick
(当然还会经过一系列的事件绑定),然后它会判断!isBatchingUpdates && !isRendering
如果正确的话就执行performSyncWork()
也就是react的指挥调度更新,正常情况下,它会等三个updates都创建完之后,再触发调度更新。但是在setTimeout的上下文它的执行环境是window,并没有isBatchingUpdates
设置true的情况,所以它就会立即执行更新。
那我们想在setTimeout里执行函数函数,也不想多次触发更新该怎么办呢,在react-dom里引用batchedUpdates
方法,并使用它就ok了。
setTimeout(() => {
batchedUpdated(() => this.count());
},0)
通过上面的过程,我们可以得到一个结论:
网友评论