鍍金池/ 教程/ HTML/ 高級性能
顯示數(shù)據(jù)
組件的引用
Controlled Input 值為 null 的情況
Reconciliation
子 props 的類型
組件的詳細說明和生命周期
傳遞 Props
特殊的非 DOM 屬性
組件 API
PureRenderMixin
雙向綁定輔助工具
瀏覽器中的工作原理
深入 JSX
表單組件
Dangerously Set innerHTML
入門
JSX 中的 If-Else
克隆組件
教程
更多的關于Refs
JSX 的 false 處理
高級性能
Mounting 后 componentWillReceiveProps 未被觸發(fā)
簡介
測試工具集
JSX 陷阱
工具集成(ToolingIntegration)
公開組件功能
通過 AJAX 加載初始數(shù)據(jù)
事件系統(tǒng)
可復用組件
this.props.children undefined
不可變數(shù)據(jù)的輔助工具(Immutability Helpers)
動態(tài)交互式用戶界面
組件的 DOM 事件監(jiān)聽
復合組件
動畫
插件
JSX 展開屬性
行內(nèi)樣式
性能分析工具
類名操作
與其他類庫并行使用 React
鍵控的片段
標簽和屬性支持
組件間的通信
React (虛擬)DOM 術語
JSX 根節(jié)點的最大數(shù)量
在樣式props中快速制定像素值
頂層 API
深入理解 React
自閉合標簽
為什么使用 React?
getInitialState 里的 Props 是一個反模式
與 DOM 的差異

高級性能

人們首先會考慮的是 React 是能否和其他非 React 版本一樣能快速和響應一個項目。重新繪制組件的整個子樹來回應每一個狀態(tài)變化的想法讓人懷疑是否這個過程中對性能產(chǎn)生負面影響。React 使用幾個巧妙的技巧,以減少所需的更新用戶界面所需要的昂貴的文檔用戶模型操作的數(shù)目。

避免調(diào)和文檔對象模型

React 使用了一個虛擬的 DOM,這是的瀏覽器中對于 DOM 樹呈現(xiàn)的一個描述符。這種并行表示形式讓 React 避免產(chǎn)生 DOM 節(jié)點和訪問現(xiàn)有的節(jié)點,比 JavaScript 對象的操作速度較慢。當一個組件的道具或狀態(tài)改變,React 決定通過構建一個新的虛擬 DOM 來進行實際的 DOM 更新和舊的 DOM 相比是否必要。只有在比較結(jié)果不一樣的情況下,Reac t盡可能少的應用轉(zhuǎn)變來融合文檔對象模型。

在此之上,React 提供了一個組件的生命周期功能,shouldComponentUpdate,這是在重新繪制過程開始之前觸發(fā)(虛擬 DOM 比較,可能最終調(diào)和 DOM),使開發(fā)人員能夠減少過程中的循環(huán)步驟。這個函數(shù)的默認實現(xiàn)返回 true,讓 React 來執(zhí)行更新:

shouldComponentUpdate: function(nextProps, nextState) {
  return true;
}

請記住,React 將非常頻繁的調(diào)用這個函數(shù),所以實現(xiàn)必須要快。 比如說你有一個由幾個聊天線程組成的消息應用程序。假設只有一個線程已經(jīng)改變。如果我們在 ChatThread 上執(zhí)行 shouldComponentUpdate,React 可以為其他線程跳過描繪步驟:

shouldComponentUpdate: function(nextProps, nextState) {
  // TODO: return whether or not current chat thread is
  // different to former one.
} 

因此,簡言之,React 避免調(diào)和 DOM 產(chǎn)生的復雜的 DOM 操作,允許用戶使用 shouldComponentUpdate 縮短過程中的循環(huán)步驟,而且,對于那些需要更新,通過對比虛擬的 DOM 來實現(xiàn)。

起作用的 shouldComponentUpdate

下面是組件的子樹。對于每一個組件表示 shouldComponentUpdate 的返回值,以及是否與虛擬的 DOM 是等價的。最后,圓的顏色指示組件是否必須調(diào)和。

http://wiki.jikexueyuan.com/project/react/images/should-component-update.png" alt="should-component-update" />

在上面的例子中,由于 shouldComponentUpdate 返回值為 false,存在 C2 中,React 沒有必要產(chǎn)生新的虛擬的 DOM,并且因此,也不需要調(diào)和 DOM。需要注意的是 react 甚至沒有在 C4 和 C5 處調(diào)用 shouldComponentUpdate。

對于 C1 和 C3 shouldComponentUpdate 返回 true,所以 React 不得不深入到葉子節(jié)點并且進行檢查。

對于 C6 它返回 true;由于和虛擬的 DOM 并不等同,它不得不調(diào)和 DOM。最后一個有趣的例子是 C8。此節(jié)點的 React 必須計算虛擬 DOM,但因為它和原來的 DOM 相同,它不需要調(diào)和 DOM。

請注意,只有 C6 需要 React 不得不對 DOM 做轉(zhuǎn)變,這是不可避免的。對于 C8 通過比較虛擬的 DOM 他不需要轉(zhuǎn)變,但是對 C2 的子樹和 C7來說,它甚至沒有計算虛擬 DOM,只需要通過執(zhí)行 shouldComponentUpdate。

所以,我們應該如何執(zhí)行 shouldComponentUpdate 呢?比如現(xiàn)有一個僅需呈現(xiàn)一個字符串值的組件:

React.createClass({
  propsTypes: {
    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;
}

到目前為止,React 處理這類簡單的道具/態(tài)結(jié)構非常簡單。我們甚至可以泛化一個基于淺層相等實現(xiàn),并混合到組件上。事實上,React 已經(jīng)提供了這樣實現(xiàn):PureRenderMixin。

但如果你的組件的道具或狀態(tài)是可變的數(shù)據(jù)結(jié)構呢?比如說組件接受的道具,而不是像'bar'的這樣的字符串,而是一個是包含,如,{FOO:'bar'}這樣一個字符串的 JavaScript 對象:

React.createClass({
  propsTypes: {
    value: React.PropTypes.object.isRequired
  },
  render: function() {
    return <div>this.props.value.foo</div>;
  }
}); 

我們之前的 shouldComponentUpdate 實現(xiàn)總是不會如我們預期一樣的實現(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

問題是當?shù)谰邔嶋H上并沒有改變時,shouldComponentUpdate 將返回 true。為了解決這個問題,我們可以用這個替代的試行方案:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value.foo !== nextProps.value.foo;
}

基本上,我們?yōu)榇_保我們正確地跟蹤變化,最后做了深刻的對比。這種做法是在性能方面相當昂貴和復雜的,它并不能擴展,因為我們要為每個模型寫不同的深度相等代碼。最重要的是,如果我們不小心管理對象的引用,它甚至可能沒有工作。比如說母節(jié)點組件的引用:

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>
    );
  }
});

在第一時間內(nèi)部組件得到呈現(xiàn)是 {FOO:'bar'},它將作為道具的值。如果用戶點擊,母組件的狀態(tài)將得到更新為 {value:{FOO:'barbar'}},引發(fā)了內(nèi)部部件在過程中重新呈現(xiàn),將接收 {foo:“barbar'} 為道具的新值。

問題是,由于母體和內(nèi)部部件共享一個參考同一個對象,當對象在第二行的 onClick 功能函數(shù)中突變時,道具的內(nèi)部部件具有將發(fā)生變化。這樣,當再描繪處理開始時,shouldComponentUpdate被調(diào)用,this.props.value.foo 將等于 nextProps.value.foo,因為事實上,this.props.valuenextProps.value引用相同的對象。

因此,由于我們錯過了道具和縮短步驟重新渲染過程中的變化,用戶界面將不會得到從 'bar''barbar' 的更新。

Immutable-JS 救援

Immutable-JS 是 Lee Byron 寫的腳本語言集合庫,其中 Facebook 最近開源 Javascript 的集合庫。它提供了通過結(jié)構性共享一成不變持久化集合。讓我們看看這些性能:

  • 不可變的:一旦創(chuàng)建,集合不能在另一個時間點改變。
  • 持久性:新的集合可以由從早先的集合和突變結(jié)合創(chuàng)建。在創(chuàng)建新的集合后,原來集合仍然有效。
  • 結(jié)構共享:使用新的集合創(chuàng)建為與對原始集合大致相同的結(jié)構,減少了拷貝的最低限度,以實現(xiàn)空間效率和可接受的性能。如果新的集合等于原始的集合,則通常會返回原來的集合。

不變性使得跟蹤更改方便;而變化將總是產(chǎn)生在新的對象,所以我們只需要檢查的已經(jīng)改變參考對象。例如,在這個 Javascript 代碼中:

var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true

雖然 y 被修改了,但因為它是對相同對象 x 的引用,所以這個比較返回 true。然而,這段代碼可以用 immutable-JS 這樣寫:

var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar'  });
var y = x.set('foo', 'baz');
x === y; // false

在這種情況下,由于 x 突變,當一個新的引用被返回,我們可以安全地假設 x 已經(jīng)改變。

另一種跟蹤變化的可能的方法是通過設置標志來做 dirty 檢查。這種方法的一個問題是,它迫使你使用 setter 或者寫很多額外的代碼,或某種類工具。或者,你可以突變之前深入對象復制并深入比較,以確定是否有變化。這種方法的一個問題是 deepCopy 和 deepCompare 是耗費大且復雜的操作。

因此,不可變的數(shù)據(jù)結(jié)構為您提供了一種廉價和更簡潔的方式來跟蹤對象的變化,這就是我們?yōu)榱藢崿F(xiàn) shouldComponentUpdate 所需要的。因此,如果我們利用 immutable-JS 提供的抽象特性來支持和聲明屬性,我們就可以使用 PureRenderMixin 并獲得 perf 的一個很好的推動。

Immutable-JS 和通量

如果你使用通量,你應該開始使用 immutable-JS 寫你的庫。看看完整的 API。

讓我們來看看使用不可變的數(shù)據(jù)結(jié)構來模擬線程的一種可行的辦法。首先,我們需要為每一個我們正在嘗試模擬的實體定義一個 record 。記錄是不可變的容器,為一組特定的域保存值:

var User = Immutable.Record({
  id: undefined,
  name: undefined,
  email: undefined
});
var Message = Immutable.Record({
  timestamp: new Date(),
  sender: undefined,
  text: ''
});

Record 函數(shù)接收的對象定義了對象的域和它們的默認值。

消息可以使用以下兩個列表來跟蹤用戶和信息:

this.users = Immutable.List();
this.messages = Immutable.List();

實現(xiàn)處理每個負載類型的功能應該是相當容易的。例如,當庫看到負載正在顯示一個新的消息,我們只要創(chuàng)建一個新的記錄,并把它添加到消息列表中:

this.messages = this.messages.push(new Message({
  timestamp: payload.timestamp,
  sender: payload.sender,
  text: payload.text
});

請注意,由于數(shù)據(jù)結(jié)構是不可改變的,我們需要把推送功能的結(jié)果分配到 this.messages 中。

在 React 中,如果我們也使用 immutable-JS 數(shù)據(jù)結(jié)構來保存組件的狀態(tài)下,我們可以混合 PureRenderMixin 到所有的組件,并且縮短重新呈現(xiàn)的過程。

上一篇:傳遞 Props下一篇:JSX 中的 If-Else