概述
在 React 16 中爲了防止沒必要要的 DOM 更新,容許你決定是否讓 .setState
更來新狀態。在調用 .setState
時返回 null
將再也不觸發更新。css
咱們將經過重構一個 mocktail (一種不含酒精的雞尾酒)選擇程序來探索它是如何工做的,即便咱們選擇相同的 mocktail 兩次也會更新。react

目錄結構以下所示:bash
src |-> App.js |-> Mocktail.js |-> index.js |-> index.css |-> Spinner.js 複製代碼複製代碼
咱們的程序如何工做
咱們的程序將顯示一個被選中的 mocktail。能夠經過單擊按鈕來選擇或切換 mocktail。這時會加載一個新的 mocktail,並在加載完成後渲染出這個 mocktail 的圖像。app
App
組件的父組件有 mocktail
狀態和 updateMocktail
方法,用於處理更新 mocktail。函數
import React, { Component } from 'react';
import Mocktail from './Mocktail';post
class App extends Component {性能
state = { mocktail: '' }動畫
updateMocktail = mocktail => this.setState({ mocktail })this
render() {spa
<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>
);
複製代碼
} }
複製代碼<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>
);
}
複製代碼
}
複製代碼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
能夠防止沒必要要的狀態更新和從新渲染,這樣使咱們的程序執行得更快,從而改善程序的用戶體驗。