React性能優化

當你們考慮在項目中使用 React 的時候,第一個問題每每是他們的應用的速度和響應是否能和非 React 版同樣,每當狀態改變的時候就從新渲染組件的整個子樹,讓你們懷疑這會不會對性能形成負面影響。React 用了一些黑科技來減小 UI 更新須要的花費較大的 DOM 操做。javascript

使用 production 版本

若是你在你的 React app 中進行性能測試或在尋找性能問題,必定要肯定你在使用 minified production build。開發者版本包括額外的警告信息,這對你在開發你的 app 的時候頗有用,可是由於要進行額外的處理,因此它也會比較慢。html

避免更新 DOM

React 使用虛擬 DOM,它是在瀏覽器中的 DOM 子樹的渲染描述,這個平行的描述讓 React 避免建立和操做 DOM 節點,這些遠比操做一個 JavaScript 對象慢。當一個組件的 props 或 state 改變,React 會構造一個新的虛擬 DOM 和舊的進行對比來決定真實 DOM 更新的必要性,只有在它們不相等的時候,React 纔會使用盡可能少的改動更新 DOM。java

在此之上,React 提供了生命週期函數 shouldComponentUpdate,在從新渲染機制迴路(虛擬 DOM 對比和 DOM 更新)以前會被觸發,賦予開發者跳過這個過程的能力。這個函數默認返回 true,讓 React 執行更新。react

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

必定要記住,React 會很是頻繁的調用這個函數,因此要確保它的執行速度夠快。git

假如你有個帶有多個對話的消息應用,若是隻有一個對話發生改變,若是咱們在 ChatThread 組件執行 shouldComponentUpdate,React 能夠跳過其餘對話的從新渲染步驟。github

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

所以,總的說,React 經過讓用戶使用 shouldComponentUpdate 減短從新渲染迴路,避免進行昂貴的更新 DOM 子樹的操做,並且這些必要的更新,須要對比虛擬 DOM。瀏覽器

shouldComponentUpdate 實戰

這裏有個組件的子樹,每個都指明瞭 shouldComponentUpdate 返回值和虛擬 DOM 是否相等,最後,圓圈的顏色表示組件是否須要更新。安全

should-component-update.png

在上面的示例中,由於 C2 的 shouldComponentUpdate 返回 false,React 就不須要生成新的虛擬 DOM,也就不須要更新 DOM,注意 React 甚至不須要調用 C4 和 C5 的 shouldComponentUpdate數據結構

C1 和 C3 的 shouldComponentUpdate 返回 true,因此 React 須要向下到葉子節點檢查它們,C6 返回 true,由於虛擬 DOM 不相等,須要更新 DOM。最後感興趣的是 C8,對於這個節點,React 須要計算虛擬 DOM,可是由於它和舊的相等,因此不須要更新 DOM。app

注意 React 只須要對 C6 進行 DOM 轉換,這是必須的。對於 C8,經過虛擬 DOM 的對比肯定它是不須要的,C2 的子樹和 C7,它們甚至不須要計算虛擬 DOM,由於 shouldComponentUpdate

那麼,咱們怎麼實現 shouldComponentUpdate 呢?好比說你有一個組件僅僅渲染一個字符串:

React.createClass({
  propTypes: {
    value: React.PropTypes.string.isRequired
  },

  render: function() {
    return <div>{this.props.value}</div>;
  }
});

咱們能夠簡單的實現 shouldComponentUpdate 以下:

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

很是好!處理這樣簡單結構的 props/state 很簡單,我門甚至能夠概括出一個基於淺對比的實現,而後把它 Mixin 到組件中。實際上 React 已經提供了這樣的實現: PureRenderMixin

可是若是你的組件的 props 或者 state 是可變的數據結構呢?好比說,組件接收的 prop 不是一個像 'bar' 這樣的字符串,而是一個包涵字符串的 JavaScript 對象,好比 { foo: 'bar' }:

React.createClass({
  propTypes: {
    value: React.PropTypes.object.isRequired
  },

  render: function() {
    return <div>{this.props.value.foo}</div>;
  }
});

前面的 shouldComponentUpdate 實現就不會一直和咱們指望的同樣工做:

// 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

這個問題是當 prop 沒有改變的時候 shouldComponentUpdate 也會返回 true。爲了解決這個問題,咱們有了這個替代實現:

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

基本上,咱們結束了使用深度對比來確保改變的正確跟蹤,這個方法在性能上的花費是很大的,由於咱們須要爲每一個 model 寫不一樣的深度對比代碼。就算這樣,若是咱們沒有處理好對象引用,它甚至不能工做,好比說這個父組件:

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

內部組件第一次渲染的時候,它會獲取 { foo: 'bar' } 做爲 value 的值。若是用戶點擊了 a 標籤,父組件的 state 會更新成 { value: { foo: 'barbar' } },觸發內部組件的從新渲染過程,內部組件會收到 { foo: 'barbar' } 做爲 value 的新的值。

這裏的問題是由於父組件和內部組件共享同一個對象的引用,當對象在 onClick 函數的第二行發生改變的時候,內部組件的屬性也發生了改變,因此當從新渲染過程開始,shouldComponentUpdate 被調用的時候,this.props.value.foonextProps.value.foo 是相等的,由於實際上 this.props.valuenextProps.value 是同一個對象的引用。

所以,咱們會丟失 prop 的改變,縮短從新渲染過程,UI 也不會從 'bar' 更新到 'barbar'

Immutable-js 來救贖

Immutable-js 是 Lee Byron 寫的 JavaScript 集合類型的庫,最近被 Facebook 開源,它經過結構共享提供不可變持久化集合類型。一塊兒看下這些特性的含義:

  • Immutable: 一旦建立,集合就不能再改變。

  • Persistent: 新的集合類型能夠經過以前的集合建立,好比 set 產生改變的集合。建立新的集合以後源集合仍然有效。

  • Structural Sharing: 新的集合會使用盡可能多的源集合的結構,減小複製來節省空間和性能友好。若是新的集合和源集合相等,通常會返回源結構。

不可變讓跟蹤改變很是簡單;每次改變都是產生新的對象,因此咱們僅須要對象的引用是否改變,好比這段簡單的 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 已經改變。

髒檢測能夠做爲另外的可行的方式追蹤改變,給 setters 一個標示。這個方法的問題是,它強制你使用 setters,並且要寫不少額外的代碼,影響你的類。或者你能夠在改變以前深拷貝對象,而後進行深對比來肯定是否是發生了改變。這個方法的問題是,深拷貝和深對比都是很花性能的操做。

所以,不可變數據結構給你提供了一個高效、簡潔的方式來跟蹤對象的改變,而跟蹤改變是實現 shouldComponentUpdate 的關鍵。因此,若是咱們使用 immutable-js 提供的抽象建立 props 和 state 模型,咱們就可使用 PureRenderMixin,並且可以得到很好的性能加強。

Immutable-js 和 Flux

若是你在使用 Flux,你應該開始使用 immutable-js 寫你的 stores,看一下 full API

讓咱們看一個可行的方式,使用不可變數據結構來給消息示例建立數據結構。首先咱們要給每一個要建模的實體定義一個 Record。Records 僅僅是一個不可變容器,裏面保存一系列具體數據:

var User = Immutable.Record({
  id: undefined,
  name: undefined,
  email: undefined
});

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

Record 方法接收一個對象,來定義字段和對應的默認數據。

消息的 store 可使用兩個 list 來跟蹤 users 和 messages:

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

實現函數處理每一個 payload 類型應該是比較簡單的,好比,當 store 看到一個表明新消息的 payload 時,咱們就建立一個新的 record,並放入消息列表:

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

注意:由於數據結構不可變,咱們須要把 push 方法的結果賦給 this.messages

在 React 裏,若是咱們也使用 immutable-js 數據結構來保存組件的 state,我門能夠把 PureRenderMixin 混入到我門全部的組件來縮短從新渲染迴路。

這篇文章是翻譯React官方文檔