React-setState杂记

15次阅读

共计 3035 个字符,预计需要花费 8 分钟才能阅读完成。

前言
在看 React 的官方文档的时候,发现了这么一句话,State Updates May Be Asynchronous,于是查询了一波资料,最后归纳成以下 3 个问题

setState 为什么要异步更新,它是怎么做的?
setState 什么时候会异步更新,什么时候会同步更新?
既然 setState 需要异步更新,为什么不让用户可以同步读到 state 的新值,但更新仍然是异步?

常见场景下的异步更新
以下是官方文档的一个例子,调用了 3 次 incrementCount 方法,期望 this.state.count 的值是 3,但最后却是 1
incrementCount() {
this.setState({count: this.state.count + 1});
}

handleSomething() {
// Let’s say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount();
// When React re-renders the component, `this.state.count` will be 1, but you expected 3.

// This is because `incrementCount()` function above reads from `this.state.count`,
// but React doesn’t update `this.state.count` until the component is re-rendered.
// So `incrementCount()` ends up reading `this.state.count` as 0 every time, and sets it to 1.

// The fix is described below!
}
那么就可以引出第一个问题
setState 为什么要异步更新,它是怎么做的?
深入源码你会发现:(引用程墨老师的 setState 何时同步更新状态)
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。
然后我在网上引用了这张图(侵删)
从结论和图都可以得出,setState 是一个 batching 的过程,React 官方认为,setState 会导致 re-rederning,而 re-rederning 的代价是昂贵的,所以他们会尽可能的把多次操作合并成一次提交。以下这段话是 Dan 在 Issue 中的回答:

中心意思大概就是:同步更新 setState 并 re-rendering 的话在大部分情况下是无益的,采用 batching 会有利于性能的提升,例如当我们在浏览器插入一个点击事件时,父子组件都调用了 setState,在 batching 的情况下,我们就不需要 re-render 两次孩子组件,并且在退出事件之前 re-render 一次即可。
那么如果我们想立即读取 state 的值,其实还有一个方法,如下代码:因为当传入的是一个函数时,state 读取的是 pending 队列中 state 的值
incrementCount() {
this.setState((state) => {
// Important: read `state` instead of `this.state` when updating.
return {count: state.count + 1}
});
}

handleSomething() {
// Let’s say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount();

// If you read `this.state.count` now, it would still be 0.
// But when React re-renders the component, it will be 3.
}
当然,仔细看 React 文档的话,可以发现,State Updates May Be Asynchronou 里面有一个 may 的字眼,也就是可能是异步更新,因而引出第二个问题
setState 什么时候会异步更新,什么时候会同步更新?
其实从第一个问题中我们就知道,React 是根据 isBatchingUpdates 来合并更新的,那么当调用 setState 的方法或者函数不是由 React 控制的话,setState 自然就是同步更新了。
简单的举下例子:

如 componentDidMount 等生命周期以及 React 的事件即为异步更新,这里不显示具体代码。
如自定义的浏览器事件,setTimeout,setInterval 等脱离 React 控制的方法,即为同步更新,如下(引用程墨老师的 setState 何时同步更新状态)

componentDidMount() {
document.querySelector(‘#btn-raw’).addEventListener(‘click’, this.onClick);
}
onClick() {
this.setState({count: this.state.count + 1});
console.log(‘# this.state’, this.state);
}
// ……
render() {
console.log(‘#enter render’);
return (
<div>
<div>{this.state.count}
<button id=”btn-raw”>Increment Raw</button>
</div>
</div>
)
}
有的人也会想能不能 React 依然合并更新,但用户可以同步读取 this.state 的值,这个问题在 React 的一个 Issue 上有提到,也是我们的第三个问题
既然 setState 需要异步更新,为什么不让用户可以同步读到 state 的新值,但更新仍然是异步?
这个问题可以直接在 Dan 的回答中得到:
This is because, in the model you proposed, this.state would be flushed immediately but this.props wouldn’t. And we can’t immediately flush this.props without re-rendering the parent, which means we would have to give up on batching (which, depending on the case, can degrade the performance very significantly).
大概意思就是说:
如果在应用中,this.state 的值是同步,但是 this.props 却不是同步的。因为 props 只有当 re-rendering 父组件后才传给子组件,那么如果要 props 变成同步的,就需要放弃 batching。但是 batching 不能放弃。

正文完
 0