React 使用虛擬 DOM,它是在瀏覽器中的 DOM 子樹的渲染描述疙剑,這個平行的描述讓 React 避免創(chuàng)建和操作 DOM 節(jié)點氯迂,這些遠比操作一個 JavaScript 對象慢。當一個組件的 props 或 state 改變核芽,React 會構造一個新的虛擬 DOM 和舊的進行對比來決定真實 DOM 更新的必要性囚戚,只有在它們不相等的時候,React 才會使用盡量少的改動更新 DOM轧简。
在此之上驰坊,React 提供了生命周期函數(shù) shouldComponentUpdate
,在重新渲染機制回路(虛擬 DOM 對比和 DOM 更新)之前會被觸發(fā)哮独,賦予開發(fā)者跳過這個過程的能力拳芙。這個函數(shù)默認返回 true
察藐,讓 React 執(zhí)行更新。
shouldComponentUpdate: function(nextProps, nextState) {
return true;
}
一定要記住舟扎,React 會非常頻繁的調用這個函數(shù)分飞,所以要確保它的執(zhí)行速度夠快。
假如你有個帶有多個對話的消息應用睹限,如果只有一個對話發(fā)生改變譬猫,如果我們在 ChatThread
組件執(zhí)行 shouldComponentUpdate
,React 可以跳過其他對話的重新渲染步驟羡疗。
shouldComponentUpdate: function(nextProps, nextState) {
// TODO: return whether or not current chat thread is
// different to former one.
}
因此染服,總的說,React 通過讓用戶使用 shouldComponentUpdate
減短重新渲染回路叨恨,避免進行昂貴的更新 DOM 子樹的操作柳刮,而且這些必要的更新,需要對比虛擬 DOM痒钝。
shouldComponentUpdate 實戰(zhàn)
這里有個組件的子樹秉颗,每一個都指明了 shouldComponentUpdate
返回值和虛擬 DOM 是否相等,最后送矩,圓圈的顏色表示組件是否需要更新蚕甥。
[圖片上傳失敗...(image-225d59-1530447751850)]
在上面的示例中,因為 C2 的 shouldComponentUpdate
返回 false益愈,React 就不需要生成新的虛擬 DOM梢灭,也就不需要更新 DOM夷家,注意 React 甚至不需要調用 C4 和 C5 的 shouldComponentUpdate
蒸其。
C1 和 C3 的 shouldComponentUpdate
返回 true
,所以 React 需要向下到葉子節(jié)點檢查它們库快,C6 返回 true
摸袁,因為虛擬 DOM 不相等,需要更新 DOM义屏。最后感興趣的是 C8靠汁,對于這個節(jié)點,React 需要計算虛擬 DOM闽铐,但是因為它和舊的相等蝶怔,所以不需要更新 DOM。
注意 React 只需要對 C6 進行 DOM 轉換兄墅,這是必須的踢星。對于 C8,通過虛擬 DOM 的對比確定它是不需要的隙咸,C2 的子樹和 C7沐悦,它們甚至不需要計算虛擬 DOM成洗,因為 shouldComponentUpdate
。
那么藏否,我們怎么實現(xiàn) shouldComponentUpdate
呢瓶殃?比如說你有一個組件僅僅渲染一個字符串:
React.createClass({
propTypes: {
value: React.PropTypes.string.isRequired
},
render: function() {
return <div>{this.props.value}</div>;
}
});
我們可以簡單的實現(xiàn) shouldComponentUpdate
如下:
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
非常好!處理這樣簡單結構的 props/state 很簡單副签,我門甚至可以歸納出一個基于淺對比的實現(xiàn)遥椿,然后把它 Mixin 到組件中。實際上 React 已經提供了這樣的實現(xiàn): PureRenderMixin
但是如果你的組件的 props 或者 state 是可變的數(shù)據(jù)結構呢淆储?比如說修壕,組件接收的 prop 不是一個像 'bar'
這樣的字符串,而是一個包涵字符串的 JavaScript 對象遏考,比如 { foo: 'bar' }
:
React.createClass({
propTypes: {
value: React.PropTypes.object.isRequired
},
render: function() {
return <div>{this.props.value.foo}</div>;
}
});
前面的 shouldComponentUpdate
實現(xiàn)就不會一直和我們期望的一樣工作:
// assume this.props.value is { foo: 'bar' }
// assume nextProps.value is { foo: 'bar' },
// but this reference is different to this.props.value
this.props.value !== nextProps.value; // true
這個問題是當 prop 沒有改變的時候 shouldComponentUpdate
也會返回 true
慈鸠。為了解決這個問題,我們有了這個替代實現(xiàn):
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value.foo !== nextProps.value.foo;
}
基本上灌具,我們結束了使用深度對比來確保改變的正確跟蹤青团,這個方法在性能上的花費是很大的,因為我們需要為每個 model 寫不同的深度對比代碼咖楣。就算這樣督笆,如果我們沒有處理好對象引用,它甚至不能工作诱贿,比如說這個父組件:
React.createClass({
getInitialState: function() {
return { value: { foo: 'bar' } };
},
onClick: function() {
var value = this.state.value;
value.foo += 'bar'; // ANTI-PATTERN!
this.setState({ value: value });
},
render: function() {
return (
<div>
<InnerComponent value={this.state.value} />
<a onClick={this.onClick}>Click me</a>
</div>
);
}
});
內部組件第一次渲染的時候娃肿,它會獲取 { foo: 'bar' }
作為 value 的值。如果用戶點擊了 a 標簽珠十,父組件的 state 會更新成 { value: { foo: 'barbar' } }
料扰,觸發(fā)內部組件的重新渲染過程,內部組件會收到 { foo: 'barbar' }
作為 value 的新的值焙蹭。
這里的問題是因為父組件和內部組件共享同一個對象的引用晒杈,當對象在 onClick
函數(shù)的第二行發(fā)生改變的時候,內部組件的屬性也發(fā)生了改變孔厉,所以當重新渲染過程開始拯钻,shouldComponentUpdate
被調用的時候,this.props.value.foo
和 nextProps.value.foo
是相等的撰豺,因為實際上 this.props.value
和 nextProps.value
是同一個對象的引用粪般。
因此,我們會丟失 prop 的改變污桦,縮短重新渲染過程亩歹,UI 也不會從 'bar'
更新到 'barbar'