5.Optimizing Performance(性能優化)

React版本:15.4.2
**翻譯:xiyoki **

在內部,React使用幾種聰明的技術來最小化更新UI所需的昂貴的DOM操作的數量。對于許多應用程序,使用React將導致快速的用戶界面,而無需進行大量工作來專門優化性能。然而,有幾種方法可以加快你的React應用程序。

Use The Production Build(使用生產構建)

在你的React應用程序中,如果你遇到了基準測試或性能方面的問題,請確保你正使用縮小的生產版本進行測試:

  • 對于創建React應用程序,你需要運行npm run build,并按照說明進行操作。
  • 對于單文件構建,我們提供production-ready min.js版本。
  • 對于Browserify,你用需要用NODE_ENV=production來運行它。
  • 對于Webpack,你需要在你的生產配置中,將其添加到插件中:
new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()
  • 對于Rollup,你需要在CommonJS插件之前使用replace插件。因此,development-only 模塊不被導入。完整的設置示例see this gist
plugins: [
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  // ...
]

當構建你的應用程序時,development build 包含的額外警告十分有用,但它額外的bookkeeping會讓其自身變慢。

Profiling Components with Chrome Timeline(使用Chrome時間軸分析組件)

development模式下,你可以在支持的瀏覽器中使用性能工具來可視化組件的加載,更新和卸載。例如:


在Chrome中執行此操作:

  1. 通過在查詢字符串中加入?react_perf來加載你的應用程序(例如,http://localhost:3000/?react_perf)。
  2. 打開Chrome DevTools Timeline選項卡,然后按住Record
  3. 執行你想配置的操作,記錄時間不要超過20秒,否則Chrome可能會掛起。
  4. 停止記錄。
  5. React事件將在User Timing標簽下分組。

請注意,數字是相對的,因此組件在生產中渲染得更快。盡管如此,這應該可以幫助你了解到不相關的UI被錯誤更新,以及你的UI更新的深度和頻率。
目前,Chrome,Edge和IE是唯一支持此功能的瀏覽器,但我們使用標準的User Timing API,因此我們希望更多的瀏覽器能支持此功能。

Avoid Reconciliation

React構建并維護已渲染的UI的內部表示。它包含從組件返回的React元素。此表示使React避免創建DOM節點和訪問現有的超出必要性的節點,因此它可能比Javascript對象上的操作更慢。有時它被稱為‘虛擬DOM’,但是它在React Native上以相同的方式工作。
當組件的props或state更改時,React通過將新返回的元素與先前已渲染的元素進行比較,來決定是否需要實際的DOM更新。當它們不相等時,React將更新DOM。
在某些情況下,你的組件可以通過覆蓋在重新渲染過程開始前就觸發的生命周期函數shouldComponentUpdate來加快這一切。該函數的默認實現是返回true,讓React執行更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

如果你知道在某些情況下你的組件不需要更新,相反,你可以從shouldComponentUpdate返回false跳過整個渲染過程,包括當前和之下組件上的render()調用。

shouldComponentUpdate In Action

這是一個組件的子樹。對于每一個節點,SCU指示shouldComponentUpdate返回的內容,而vDOMEq指示渲染的React元素是否等效。最后,圓圈的顏色表示組件是否必須reconciled 。


對于以C2為根的子樹,由于shouldComponentUpdate返回false,React沒有嘗試渲染C2,因此甚至不必在C4和C5上調用shouldComponentUpdate
對于C1和C3,shouldComponentUpdate返回true,所以React必須下到葉子檢查它們。對于C6,shouldComponentUpdate返回true,并且因為渲染的元素不等價,React不得不更新DOM。
最后一個有趣的例子是C8。React不得不渲染這個組件,但是由于它返回的React元素等于先前已渲染的元素,所以它不必更新DOM。
注意React只需要為C6做DOM更新,這是不可避免的。對于C8,它通過比較已渲染的React元素解脫(bailed out)了。對于C2的子樹和C7,由于我們已從shouldComponentUpdate上解脫(bailed out),它甚至沒有必要對元素進行比較,并且render也沒有被調用。

Examples

props.colorstate.count變量的改變是你組件改變的唯一方式時,你可以使用shouldComponentUpdate檢查:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

這段代碼,shouldComponentUpdate只是檢查props.colorstate.count是否有任何變化。如果這些值不更改,組件不更新。如果你的組件更復雜,你可以使用一個類似的模式在propsstate的所有字段之間做一個淺比較,以確定組件是否應該更新。
這個模式是常見的,React提供了從React.PureComponent繼承而來的logic-just的用法。所以這段代碼以一個更簡單的方法來實現相同的事情:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

大多數時候,你可以使用React.PureComponent,而不是自己寫shouldComponentUpdate。它只做一個淺的比較,因此如果props或state以一個淺比較會錯過的方式被改變,你就不能使用它。
這可能是更復雜的數據結構的問題。例如,假設你想要一個ListOfWords組件來渲染一個以逗號分隔的單詞列表,有一個WordAdder父組件,讓你點擊按鈕添加一個單詞到單詞列表中。此代碼無法正常工作:

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

問題在于,PureComponent將在this.props.words的新值和舊值之間做一個簡單的比較。由于該代碼在WordAdder組件的handleClick方法中改變words數組,因此this.props.words的新值和舊值比較后依然相等。該ListOfWords因此將不會更新,即使它應該渲染新的單詞。

The Power Of Not Mutating Data

避免此問題的最簡單的方法是避免使用正作為props或state使用的mutating values。例如,上面的handleClick方法可以用concat重寫為:

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

ES6支持數組的擴展語法,可以使這更容易。如果你正在使用Create React App,此語法默認可用。

handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

你也可以重寫代碼,以類似的方式mutates對象,以避免mutation。例如,我們有一個名為colormap的對象,我們要寫一個將colormap.right改變成'blue'的函數。我們可以寫:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

要寫這個,而不改變原始對象,我們可以使用Object.assign方法:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

updateColorMap現在返回一個新對象,而不是改變舊的對象。Object.assign是ES6新增的,需要polyfill。
有一個添加 object spread properties的Javascript 提議,使得更新對象更容易,也不會有mutation:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

如果你使用Create React App,默認情況下,Object.assign和object spread syntax都有效。

Using Immutable Data Structures(使用不可變數據結構)

Immutable.js 是另一個解決這個問題的方法。它提供了不可變,持久的集合,通過結構共享工作:

  • 不可變:一旦創建,集合不能在另一個時間點更改。
  • 持久性:新集合可以從先前的集合和諸如set的mutation中創建而來。新集合創建后,原始集合仍然有效。
  • 結構共享:使用與原始集合相同的結構創建新集合,從而將復制減少到最低程度以提高性能。

不變性使跟蹤變化更便宜。變化將始終導致一個新對象,因此我們只需要檢查對對象的引用是否已更改。例如,在這個常規的Javascript代碼中:

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

雖然y被編輯,但由于它和x都是對同一個對象的引用,這個比較返回true。你可以用immutable.js編寫類似的代碼:

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

在這種情況下,由于在變異時返回一個新的引用x,我們可以安全地假設x已經改變。
另外兩個可以幫助使用不可變數據的庫是 seamless-immutableimmutability-helper.
不可變的數據結構為你提供了一種便宜的方式來跟蹤對象的更改,這是我們實現shouldComponentUpdate所需要的。這可以為你提供一個不錯的性能提升。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 深入JSX date:20170412筆記原文其實JSX是React.createElement(componen...
    gaoer1938閱讀 8,107評論 2 35
  • 自己最近的項目是基于react的,于是讀了一遍react的文檔,做了一些記錄(除了REFERENCE部分還沒開始讀...
    潘逸飛閱讀 3,496評論 1 10
  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,311評論 0 2
  • 以下內容是我在學習和研究React時,對React的特性、重點和注意事項的提取、精練和總結,可以做為React特性...
    科研者閱讀 8,297評論 2 21
  • 每天晚上都會和小盆友一起讀書,有時是我讀給他,有時是他讀給我。 每次我們都會把認為最好的句子畫下來...
    半調兒貓閱讀 183評論 0 0