標籤:對象 cte awk 實踐 產生 個人 blank import highlight
轉自:http://www.tuicool.com/articles/Ar6Zruq
React本身就非常關注效能,其提供的虛擬DOM搭配上Diff演算法,實現對DOM操作最小粒度的改變也是非常的高效。然而其組件渲染機制,也決定了在對組件進行更新時還可以進行更細緻的最佳化。
react組件渲染
react的組件渲染分為初始化渲染和更新渲染。
在初始化渲染的時候會調用根組件下的所有組件的render方法進行渲染,如(綠色表示已渲染,這一層是沒有問題的):
但是當我們要更新某個子組件的時候,如的綠色組件(從根組件傳遞下來應用在綠色組件上的資料發生改變):
我們的理想狀態是只調用關鍵路徑上組件的render,如:
但是react的預設做法是調用所有組件的render,再對產生的虛擬DOM進行對比,如不變則不進行更新。這樣的render和虛擬DOM的對比明顯是在浪費,如(黃色表示浪費的render和虛擬DOM對比)
那麼如何避免發生這個浪費問題呢,這就要牽出我們的 shouldComponentUpdate
shouldComponentUpdate
react在每個組件生命週期更新的時候都會調用一個 shouldComponentUpdate(nextProps, nextState)
函數。它的職責就是返回true或false,true表示需要更新,false表示不需要,預設返回為true,即便你沒有顯示地定義 shouldComponentUpdate 函數。這就不難解釋上面發生的資源浪費了。
為了進一步說明問題,我們再引用一張官網的圖來解釋,如( SCU表示shouldComponentUpdate,綠色表示返回true(需要更新),紅色表示返回false(不需要更新);vDOMEq表示虛擬DOM比對,綠色表示一致(不需要更新),紅色表示發生改變(需要更新)):
根據渲染流程,首先會判斷shouldComponentUpdate(SCU)是否需要更新。如果需要更新,則調用組件的render產生新的虛擬DOM,然後再與舊的虛擬DOM對比(vDOMEq),如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM;如果SCU不需要更新,則直接保持不變,同時其子項目也保持不變。
C1根節點,綠色SCU (true),表示需要更新,然後vDOMEq紅色,表示虛擬DOM不一致,需要更新。
C2節點,紅色SCU (false),表示不需要更新,所以C4,C5均不再進行檢查
C3節點同C1,需要更新
C6節點,綠色SCU (true),表示需要更新,然後vDOMEq紅色,表示虛擬DOM不一致,更新DOM。
C7節點同C2
C8節點,綠色SCU (true),表示需要更新,然後vDOMEq綠色,表示虛擬DOM一致,不更新DOM。
為了避免一定程度的浪費,react官方還在0.14版本中加入了無狀態組件,如下:
// es5function HelloMessage(props) { return <div>Hello {props.name}</div>;}
// es6const HelloMessage = (props) => <div>Hello {props.name}</div>;
具體可參考官網: Reusable Components
既然明白了這關鍵所在,現在是時候向我們的大大小小一籮筐組件開刀了。
牛刀小試,直接把一些不需要更新的組件返回false
下面我們以音量表徵圖為例,這是一個svg表徵圖,不需要更新,所以直接return false
import React, {Component} from ‘react‘;class Mic extends Component { constructor(props) { super(props); } shouldComponentUpdate() { return false; } render() { return ( <svg className="icon-svg icon-mic" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title"> <title>mic</title> <path className="path1" d="M15 22c2.761 0 5-2.239 5-5v-12c0-2.761-2.239-5-5-5s-5 2.239-5 5v12c0 2.761 2.239 5 5 5zM22 14v3c0 3.866-3.134 7-7 7s-7-3.134-7-7v-3h-2v3c0 4.632 3.5 8.447 8 8.944v4.056h-4v2h10v-2h-4v-4.056c4.5-0.497 8-4.312 8-8.944v-3h-2z"></path> </svg> ) }}export default Mic;
登堂入室,對資料進行對比確定是否需要更新
先來個官網的例子,通過判斷id是否改變來確定是否需要更新:
shouldComponentUpdate: function(nextProps, nextState) { return nextProps.id !== this.props.id;}
看起來也沒那麼玄乎,直接一個 !==
對比下就ok了,那是不是所有的都可以這樣直接對比就可以呢? 我們先來看下js的兩個資料類型(原始類型與參考型別)的各自比較
// 原始類型var a = ‘hello the‘;var b = a;b = b + ‘world‘;console.log(a === b); // false// 參考型別var c = [‘hello‘, ‘the‘];var d = c;d.push(‘world‘);console.log(c === d); // true
我們可以看到a和b不等,但是c和d是一樣一樣的,這修改了d,也直接修改了c,那還怎麼對比(關於原始類型與參考型別的區別這裡就不說明了)。
現在看來我們得分情況處理了,原始類型資料和參考型別資料得採用不同的辦法處理。
原始類型資料
這沒什麼好說的,直接比對就是了。但是每個人都是想偷懶的,這要是每個組件都要這樣去寫下也挺麻煩的,於是react官方有了外掛程式幫我們搞定這事:
PureRenderMixin (es5的外掛程式)
var PureRenderMixin = require(‘react-addons-pure-render-mixin‘);React.createClass({ mixins: [PureRenderMixin], render: function() { return <div className={this.props.className}>foo</div>; }});
Shallow Compare (es6的外掛程式)
var shallowCompare = require(‘react-addons-shallow-compare‘);export class SampleComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } render() { return <div className={this.props.className}>foo</div>; }}
參考型別資料
既然參考型別資料一直返回true,那就得想辦法處理,能不能把前後的資料變成不一樣的引用呢,那樣不就不相等了嗎?於是就有了我們的不可變資料。
react官方提供了一個 Immutability Helpers
const newValue = { ...oldValue // 在這裡做你想要的修改};// 快速檢查 —— 只要檢查引用newValue === oldValue; // false// 如果你願意也可以用 Object.assign 文法const newValue2 = Object.assign({}, oldValue);newValue2 === oldValue; // false
然後在 shouldComponentUpdate
中進行比對
shouldComponentUpdate(nextProps) {
return isObjectEqual(this.props, nextProps);}
我們目前採用的是在reducer裡面更新資料使用 Object.assign({}, state, {newkey: newValue}
(資料管理採用redux),然後在組件裡面根據某個具體的欄位判斷是否更新,如title或id等,而不是判斷整個對象:
shouldComponentUpdate: function(nextProps, nextState){ return nextProps.title !== this.props.title;}
React 組件效能最佳化探索實踐