<div class="article-content">
<p>本文基于React15总结,最新React16可能有一些出入,望周知!!!</p>
<h3 class="heading">setState真的是异步的吗 ?</h3>
<p><strong>举个?</strong></p>
<pre class="blockcode"><code class="hljs bash copyable">class Example extends React.Component {
<span class="hljs-function"><span class="hljs-title">constructor</span></span>() {
super();
this.state = {
val: 0
};
}
<span class="hljs-function"><span class="hljs-title">componentDidMount</span></span>() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 <span class="hljs-built_in">log</span>
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 <span class="hljs-built_in">log</span>
<span class="hljs-built_in">set</span>Timeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 <span class="hljs-built_in">log</span>
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 <span class="hljs-built_in">log</span>
}, 0);
}
<span class="hljs-function"><span class="hljs-title">render</span></span>() {
<span class="hljs-built_in">return</span> null;
}
};
<span class="copy-code-btn">复制代码</span></code></pre>
<p>问上述代码中 4 次 console.log 打印出来的 val 分别是多少? 不卖关子,先揭晓答案,4 次 log 的值分别是:0、0、2、3。 若结果和你心中的答案不完全相同,那下面的内容你可能会感兴趣。 同样的 setState 调用,为何表现和结果却大相径庭呢?让我们先看看 setState 到底干了什么。</p>
<h3 class="heading">先放结论:</h3>
<ul><li>setState 只在<strong>合成事件和钩子函数中是“异步”的</strong>,在<strong>原生事件和setTimeout 中都是同步</strong>的。</li><li>setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。</li><li>setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,<strong>在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。</strong></li></ul>
<p>所以基于上述结论,如果想要实现上述代码中 4 次 console.log 打印出来的 val 分别是1、2、3、4。可以实现如下:</p>
<pre class="blockcode"><code class="hljs bash copyable"><span class="hljs-built_in">set</span>Timeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 1
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 2
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 3
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 4
}, 0);
<span class="copy-code-btn">复制代码</span></code></pre>
<p>或者</p>
<pre class="blockcode"><code class="hljs bash copyable">this.setState((prevState) => {
<span class="hljs-built_in">return</span> { count: prevState.val + 1 }
})
console.log(this.state.val); // 1
this.setState((prevState) => {
<span class="hljs-built_in">return</span> { count: prevState.val + 1 }
})
console.log(this.state.val); // 2
this.setState((prevState) => {
<span class="hljs-built_in">return</span> { count: prevState.val + 1 }
})
console.log(this.state.val); // 3
this.setState((prevState) => {
<span class="hljs-built_in">return</span> { count: prevState.val + 1 }
})
console.log(this.state.val); // 4
<span class="copy-code-btn">复制代码</span></code></pre>
<h3 class="heading">setState 干了什么</h3>
<p></p>
<figure>
<figcaption></figcaption>
</figure>
<p></p>
<p>上面这个流程图是一个简化的 setState 调用栈,setState 方法由父类 Component 提供(因为组件本身继承自React.Component),是 React 组件修改局部状态的方法。</p>
<pre class="blockcode"><code class="hljs bash copyable">// src/isomorphic/modern/class/ReactComponent.js
ReactComponent.prototype.setState = <span class="hljs-keyword">function</span>(partialState, callback) {
// ...
this.updater.enqueueSetState(this, partialState);
<span class="hljs-keyword">if</span> (callback) {
this.updater.enqueueCallback(this, callback);
}
};
<span class="copy-code-btn">复制代码</span></code></pre>
<pre class="blockcode"><code class="hljs bash copyable">// src/renderers/shared/reconciler/ReactUpdateQueue.js
enqueueSetState: <span class="hljs-keyword">function</span>(publicInstance, partialState) {
// 获取 ReactComponent 组件对象(这里的组件对象指的是调用了this.setState的组件)
va |
|