概述
在 React 16 中为了防止不必要的 DOM 更新,允许你决定是否让 .setState
更来新状态。在调用 .setState
时返回 null
将不再触发更新。
我们将通过重构一个 mocktail (一种不含酒精的鸡尾酒)选择程序来探索它是如何工作的,即使我们选择相同的 mocktail 两次也会更新。
目录结构如下所示:
src
|-> App.js
|-> Mocktail.js
|-> index.js
|-> index.css
|-> Spinner.js
复制代码复制代码
我们的程序如何工作
我们的程序将显示一个被选中的 mocktail。可以通过单击按钮来选择或切换 mocktail。这时会加载一个新的 mocktail,并在加载完成后渲染出这个 mocktail 的图像。
App
组件的父组件有 mocktail
状态和 updateMocktail
方法,用于处理更新 mocktail。
import React, { Component } from 'react';
import Mocktail from './Mocktail';
class App extends Component {
state = { mocktail: '' }
updateMocktail = mocktail => this.setState({ mocktail })
render() {
<span class="hljs-keyword">const</span> mocktails = [<span class="hljs-string">'Cosmopolitan'</span>, <span class="hljs-string">'Mojito'</span>, <span class="hljs-string">'Blue Lagoon'</span>];
<span class="hljs-keyword">return</span> (
<React.Fragment>
<header>
<h1>Select Your Mocktail</h1>
<nav>
{
mocktails.map((mocktail) => {
return <button
key={mocktail}
value={mocktail}
type="button"
onClick={e => this.updateMocktail(e.target.value)}>{mocktail}</button>
})
}
</nav>
</header>
<main>
<Mocktail mocktail={this.state.mocktail} />
</main>
</React.Fragment>
);
复制代码
} }
复制代码
export default App; 复制代码复制代码
在 button
元素的 onClick
事件上调用 updateMocktail
方法,mocktail
状态被传递给子组件 Mocktail
。
Mocktail
组件有一个名为 isLoading
的加载状态,当其为 true
时会渲染 Spinner
组件。
import React, { Component } from 'react';
import Spinner from './Spinner';
class Mocktail extends Component {
state = {
<span class="hljs-attr">isLoading</span>: <span class="hljs-literal">false</span>
}
componentWillReceiveProps() {
<span class="hljs-keyword">this</span>.setState({ <span class="hljs-attr">isLoading</span>: <span class="hljs-literal">true</span> });
setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =></span>
<span class="hljs-keyword">this</span>.setState({
<span class="hljs-attr">isLoading</span>: <span class="hljs-literal">false</span>
}), <span class="hljs-number">500</span>);
}
render() {
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.state.isLoading) {
<span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">Spinner</span>/></span></span>
}
<span class="hljs-keyword">return</span> (
<React.Fragment>
<div className="mocktail-image">
<img src={`img/${this.props.mocktail.replace(/ +/g, "").toLowerCase()}.png`} alt={this.props.mocktail} />
</div>
</React.Fragment>
);
}
复制代码
}
复制代码
export default Mocktail; 复制代码复制代码
在 Mocktail
组件的 componentWillReceiveProps
生命周期方法中调用 setTimeout
,将加载状态设置为 true
达 500 毫秒。
每次使用新的 mocktail
状态更新 Mocktail
组件的 props 时,它会用半秒钟显示加载动画,然后渲染 mocktail 图像。
问题
现在的问题是,即使状态没有改变,mocktail
状态也会被更新,同时触发重新渲染 Mocktail
组件。
例如每当单击 Mojito 按钮时,我们都会看到程序对 Mojito 图像进行了不必要地重新渲染。 React 16 对状态性能进行了改进,如果新的状态值与其现有值相同的话,通过在 setState
中返回 null
来防止来触发更新。
解决方案
以下是我们将要遵循的步骤,来防止不必要的重新渲染:
- 检查新的状态值是否与现有值相同
- 如果值相同,我们将返回
null
- 返回
null
将不会更新状态和触发组件重新渲染
首先,在 app
组件的 updateMocktail
方法中,创建一个名为 newMocktail
的常量,并用传入的 mocktail
值为其赋值。
updateMocktail = mocktail => {
const newMocktail = mocktail;
this.setState({
mocktail
})
}
复制代码复制代码
因为我们需要基于之前的状态检查和设置状态,而不是传递 setState
和 object
,所以我们需要传递一个以前的状态作为参数的函数。然后检查 mocktail
状态的新值是否与现有值相同。
如果值相同,setState
将返回 null
。否则 setState
返回更新的 mocktail
状态,这将触发使用新状态重新渲染 Mocktail
组件。
updateMocktail = mocktail => {
const newMocktail = mocktail;
this.setState(state => {
if (state.mocktail === newMocktail) {
return null;
} else {
return { mocktail };
}
})
}
复制代码复制代码
现在单击按钮仍会加载其各自的 mocktail 图像。但是,如果我们再次单击同一个mocktail按钮,React 不会重新渲染 Mocktail
组件,因为 setState
返回 null
,所以状态没有改变,也就不会触发更新。
我在下面的两个 GIF 中突出显示了 React DevTools 中的更新:
**注意:**我在这里换了一个深色主题,以便更容易观察到 React DOM 中的更新。
总结
本文介绍了在 React 16 中怎样从 setState
返回 null
。我在下面的 CodeSandbox
中添加了 mocktail
选择程序的完整代码,供你使用和 fork。
CodeSandbox:codesandbox.io/embed/vj8wk…
通过使用 null
可以防止不必要的状态更新和重新渲染,这样使我们的程序执行得更快,从而改善程序的用户体验。