React文檔(十八)最佳性能

在內部,React使用好幾種聰明的技巧去最小化更新UI所須要的DOM操做。對於不少應用來講,使用React會使得構建用戶界面很是之快並且不須要作太多專門的性能優化。雖然如此,仍是有一些方法可讓你爲React應用加速。react

使用生產構建webpack

若是你正在性能測試或者在你的應用裏遇到性能測試問題,確保你測試時使用了壓縮了的生產構建:git

  • 對於建立React應用,你須要運行npm run build而後遵循指令
  • 對於單文件構建,咱們提供生產環境.min.js的文件版本
  • 對於模塊管理,你須要設置NPDE_ENV=production
  • 對於webpack,你須要添加這個到配置文件的plugins裏
new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()
  • 對於彙總,你須要在commonjs插件以前使用replace插件所以只在開發環境使用的模塊就不會被導入。完整的設置例子請看這裏
plugins: [
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  // ...
]

開發的構建包括了額外的警告頗有幫助可是因爲額外的統計因此會讓程序變慢。github

使用chrome performance對組件進行性能分析web

在開發模式下,你能夠經過使用瀏覽器裏的性能工具來顯示組件的實例化,更新和銷燬。舉個例子:chrome

在chrome瀏覽器裏這樣作:npm

  1. 加載你的應用,使地址url的查詢字符串爲?react_perf
  2. 打開chrome開發者工具的performance面板而且按下record
  3. 而後作一些你想要測試分析的動做。不要錄製超過20秒不然chrome可能會掛起
  4. 中止錄製
  5. React事件將會成組地出如今user timing標籤下面

注意那些數字是相對的所以組件在生產環境下會渲染地更快。還有,這樣能夠幫助你意識到不相關的UI會錯誤的更新,還有UI更新的深度和頻率。數組

現在的chrome,edge和IE瀏覽器支持這個特性,可是咱們使用的標準user timing API所以咱們但願更多的瀏覽器能夠添加對它的支持。瀏覽器

避免重複渲染性能優化

React在渲染出的UI內部創建和維護了一個內層的實現方式。這個內部表示包含了從組建裏返回的React元素。這個內部表示讓React避免了沒必要要的建立和關聯DOM節點,那樣會使速度變慢。有時被提到爲「虛擬DOM」,可是在React Native裏它一樣存在。

當一個組件的props或者state改變了,React經過比較新返回的元素和以前渲染的元素來決定是否一個DOM的更新是必要的。當二者不同的時候,React會更新DOM。

在一些狀況下,你的組件經過重寫生命週期函數shouldComponentUpdate能夠爲程序加速,shouldComponentUpdate是在從新渲染的流程開始以前被觸發。這個函數默認會返回true,委託React去更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

若是你知道在某些狀況下你的組件不須要更新,你能夠在shouldComponentUpdate裏返回false,來跳過整個渲染流程,包括對該組件和以後的內容調用render()方法。

shouldComponentUpdate應用

下面是組件的樹狀目錄。對於每個節點,SCU指明瞭shouldComponentUpdate返回了什麼,而vDOMEq指明瞭是否已經渲染的React元素髮生了變化。最終,圓圈的顏色代表了是否組件須要從新渲染。

自從shouldComponentUpdate在樹的節點C2處返回了false,React不會試圖渲染C2節點,所以在C4和C5節點上也不須要調用shouldComponentUpdate。

對於C1和C3節點,shouldComponentUpdate返回了true,所以React必須往下到葉子節點去檢查它們。對於C6shouldComponentUpdate返回了true,自從元素已經發生改變React就必須更新DOM。

最有趣的狀況是C8。React必須渲染這個組件,可是自從React元素返回的和以前渲染的同樣,那就沒必要更新DOM。

注意React只是必須改變C6的DOM,這是不可避免的。對於C8,它經過比較跳出了更新,而且對於C2的子樹和C7,甚至不須要比較由於shouldComponentUpdate返回了false,因此render就不會被調用。

例子

若是想讓組件只在props.color或者state.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.color和stete.count的值是否有變化。若是它們沒有變化,那麼組件就不更新。若是你的組件越複雜,你就能夠對於props和state使用「表面對比」相似的模式來決定是否組件應該更新。這個模式很常見,React提供了一個幫助工具來實現這個邏輯,它繼承自React.PureComponent。因此下面的代碼用簡單的方式實現了一樣的事:

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所以就不會更新即便它擁有了新的單詞。

不會突變的數據的力量

最簡單的方法去避免這個問題就是避免去使用可能會突變的props或者state。舉個例子,上面的handleClick方法可使用concat重寫:

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}
ES6支持一種對於數組的擴展操做符可讓這裏更簡單。若是你是建立React App,那麼這個語法默認是可用的。
handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

你也能夠重寫改變對象的代碼爲了不這個突變,經過相似的方式。舉個例子,咱們有一個對象名字叫作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。

這裏有一個js建議要添加對象擴展操做符使得不修改而更新對象更加簡便:

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

若是你正在建立React App,Object.assign和擴展操做符語法都默認是可用的。

使用不可變的數據結構

Immutable.js是解決這個問題的另外一種方法。它經過結構共享提供不可突變的,持久的集合:
  • 不可變的:一旦建立,一個合集在其餘時間點不能被改變。
  • 執着的:新的合集能夠經過前一個合集和一個改變來創建。原始的集合在新的集合創建後依然可用。
  • 結構分享:新的合集儘量多的使用和原始集合一樣的結構來建立,減小複製到最低限度來提升性能。

不可變性使得追蹤改變很簡單。每一個變化都會致使產生一個新的對象,所以咱們只需檢查索引對象是否改變。舉個例子,在這段js代碼中:

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-immutable和immutability-helper。

不可變的數據結構提供了方便的方式來追蹤對象的變化,這就是咱們須要的東西來實現shouldComponentUpdate。這樣你就能夠得到一個很好的性能提升。

相關文章
相關標籤/搜索