爲何React和Immutable是好朋友

  目前工做中用到了React,搭配一塊兒使用了Immutable.js。以前沒有靜下來思考一下爲何React社區這麼推崇搭配一塊兒使用Immutable。正好想寫篇文章分析一下這個問題。以前我翻譯了React官方文檔中Advanced Guides中關於一致化處理(Reconciliation)性能優化(Optimizing Performance)就涉及到這方面的內容。javascript

React性能優化

  一提到React,你們第一時間就想到的虛擬DOM(Virtual DOM)和伴隨其帶來的高性能。可是React提供的是聲明式的API(declarative API),好的一方面是讓咱們編寫程序更加方便,但另外一方面,卻使得咱們不太瞭解內部細節。java

一致化處理(Reconciliation)

  React採用的是虛擬DOM,每次屬性(props)和狀態(state)發生變化的時候,render函數返回不一樣的元素樹,React會檢測當前返回的元素樹和上次渲染的元素樹以前的差別,而後找出何如高效的更新UI。react

react-tree

  上圖展現的就是一個元素樹,React比較兩次元素樹差別的時候,首先從根節點開始。若是元素類型不相同時,該節點如下(包括當前節點)的元素樹都會被銷燬,樹的根節點如下的任何組件都會被卸載,所以狀態(state)也會丟失。若是元素節點類型是相同的,那就須要區分是DOM元素仍是組件。若是是DOM元素,會保持節點相同,僅更新改變的屬性。而若是是組件的話,會保持組件實例不變,僅更新組件實例的屬性,所以組件實例的狀態(state)就會被保留下來。比較完當前節點,而後會遞歸遍歷比較子元素。git

  一致化處理(Reconciliation)包括的就是React元素的比較以及對應的React元素不一樣時對DOM的更新,便可理解爲React 內部將虛擬 DOM 同步更新到真實 DOM 的過程,包括新舊虛擬 DOM 的比較及計算最小 DOM 操做。咱們能夠看到促使React性能提高的一個重要點就是避免一致化處理。github

shouldComponentUpdate

  React使用shouldComponentUpdate來判別組件是否會由於當前屬性(props)和狀態(state)變化而致使組件輸出變化。默認的shouldComponentUpdate會在props和state發生變化時返回true,表示組件會從新渲染,從而調用render函數。固然了在首次渲染的時候和使用forceUpdate的時候,是不會通過shouldComponentUpdate判斷。shouldComponentUpdate做爲性能優化的一個很是有用且簡單的方法,很是實用。   性能優化

舉例

  咱們以React官網中的圖做爲實例:ide

diff.png

  SCU表明shouldComponentUpdate,紅色SCU表示shouldComponentUpdate返回true,綠色的SCU表示shouldComponentUpdate返回false。vDOMEq表明渲染的React元素是否相等。紅色vDOMEq表示React元素不相等,綠色的vDOMEq表示React元素相等。元素節點爲紅色表示須要對該節點進行一致化處理,節點顏色爲綠色表示不須要對其進行一致化處理。函數

  表示對於兩棵元素樹,React會同步比較。在比較C1節點,由於SCU返回的false,須要對其進行diff,vDOMEq返回的是false,故須要一致化處理,存在DOM元素的更新。迭代遞歸到C2,由於SCU返回的是true,以C2爲根節點的整個子樹,都不須要diff判斷。可是C3的SCU返回true,須要進行diff比較。C3的子節點C6由於SCU返回true須要進行diff比較,而且由於vDOMEq返回的false,所以C6不可避免進行DOM的更新。對於C8來說,經過比較渲染元素而不須要進行一致化處理,而C7由於shouldComponentUpdate返回false從而不須要進行diff。性能

  所以咱們能夠發現,若是可以合理地編寫shouldComponentUpdate函數,從而能避免沒必要要的一致化處理,使得性能能夠極大提升。通常shouldComponentUpdate會比較propsstate中的屬性是否發生改變(淺比較)來斷定是否shouldComponentUpdate是否須要返回true從而觸發一致化處理。咱們能夠經過繼承React.PureComponent或者經過引入PureRenderMixin模塊來達到目的。可是這也存在一個問題:優化

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This section is bad style and causes a bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}複製代碼

  組件展示一個以逗號分隔的單詞列表,在父組件WordAdder,當你點擊一個按鈕時會給列表添加一個單詞,但實際上,上面的代碼是存在問題的,ListOfWords繼承的React.PureComponent,當你每次點擊按鈕會給WordAdder組件中的this.state.words添加新的單詞。所以ListOfWords中的shouldComponentUpdate在判斷this.props.wordsnextProps.words實際是相等的,所以返回了false。因此,ListOfWords是不會被從新渲染的,由於React.PureComponent中的shouldComponentUpdate進行的是淺比較(shallow comparison),可是若是真的進行深比較,那麼比較的性能損耗又太大,不由讓咱們得出一個結論:

共享的可變狀態是萬惡之源

這時候Immutable.js橫空出世

Immutable Data

  Immutable Data是指一旦建立,就不能被更改的數據。對Immutable對象的修改都會返回新的Immutable對象。而且目前的Immutable庫,都實現告終構共享,即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享,避免了deepCopy把全部節點都複製一遍帶來的性能損耗。比較兩個Immutable對象是否相同,只須要使用===就能夠輕鬆判別。所以若是React傳入的數據是Immutable Data,那麼React就能高效地比較先後屬性的變化,從而決定shouldComponentUpdate的返回值。解決了上面存在的問題。  可是引入Immutable Data也不是沒有代價的,畢竟Immutable Data須要引入新的API,而且須要引入新的庫,在原有的項目中引入Immutable Data也是有風險和代價的並且還須要開發者轉變原有的思惟(畢竟天下沒有白吃的午飯)。

相關文章
相關標籤/搜索