【譯】 React 性能工程(上)

04 February 2016 on Reacthtml

本文是 React 性能工程系列文章的第一篇(共兩篇) 第二篇 深刻探討React性能調試 譯文 如今已經推出!react

這篇文章適用於複雜的React應用。若是隻是構建一些簡單的、小型的應用,你還不用考慮性能問題。沒必要過早地優化,去構建吧!git

然而,若是你是在構建一個DNA設計工具、一個膠體圖片分析器、一個富文本編輯器,或者一個全能的電子數據表,你就會觸碰到性能的瓶頸了。這時候,就有必要來解決這個問題了。在構建Benchling這個項目的過程當中,咱們遇到了不少問題。因此,本文的目的是給那些網絡開發者和關注Benchling的粉絲分享咱們學到的一些方法。(固然,若是你喜歡這類問題,咱們正在招聘!)github

在這篇文章中,我將會講述使用React性能工具的一些基礎知識、一些會致使React渲染瓶頸的常見問題,以及一些須要謹記的調試方法。web

基準

瀏覽器性能能夠用三句話來概述:理想中你指望瀏覽器每秒渲染60幀,每幀16.7毫秒。當你的app運行緩慢的時候,常常須要很長時間才能響應用戶事件、處理數據或者從新渲染新的數據。大多數狀況下,你並無時刻在處理複雜的數據,只是浪費時間在重繪而已。chrome

使用React, 不須要作額外的工做,就能夠取得性能上的優點:npm

由於React會處理全部的DOM操做,很大程度上免去了DOM解析和佈局所帶來的問題。在後臺,React會在JavaScript中維持虛擬DOM, 這樣便於快速地把文檔更新到指望狀態。segmentfault

咱們要避免直接操做DOM,由於React組件的狀態是儲存在JS中的。一個傳統的性能問題就是在不恰當的時刻操做DOM,這樣會致使像被迫同步佈局這樣的問題(例如:爲了獲取某節點的樣式 someNode.style.left, 使得瀏覽器被迫渲染畫面)。爲了避免用如下這種作法:數組

`someNode.style.left = parseInt(someNode.style.left) + 10 + "px";`

咱們能夠聲明式地調用 `` 來觸發組件,不須要從DOM元素讀取數據,就能夠簡單地更新狀態了。瀏覽器

`this.setState({left: this.state.left + 10}).`

說明一點,這些優化不用React也是能夠實現的,我只是簡單地指出React趨向於提早解決這些問題。

對於簡單的應用,React 所帶來的這些性能優化就足夠了。我認爲這些是使框架變得可行的最小工做量了。然而,當你開發的頁面愈來愈多、越複雜時,維護和對比虛擬DOM就會變成一項昂貴的操做了。幸運的是,React提供了一些工具,能夠檢測哪裏有性能問題,便於你及時地避開這些問題。

調試帶來的性能問題

請注意 -- 調試自己也會帶來一些問題,致使混淆調試部分,覺得這部分不會留在生產中。

元素窗口

元素窗口是觀察DOM元素是否被從新渲染的一個簡單好用的途徑,當一個屬性改變或者一個DOM節點更新、插入、替換時,它都會閃現一個顏色。然而,元素面板的閃現,或者說是從新渲染也將影響到性能!常常我會從元素窗口切換到控制檯,來更準確地感知每秒的幀數。

PropTypes

在用進行React開發時,當一個組件被渲染時,常常要進行PropType 校驗。組件所接收到的 prop 先被檢測來幫助調試和開發。使用 Chrome 提供的 JS Profiler ,你能夠發現React組件在這個校驗的方法上花費了很長一段時間。

儘管開發環境的警告提示有助於調試,但它們是會有一些性能方面的代價的,這些代價則不會反映在生產環境。有時我會使用切換到生產構建環境來忽略這種遲緩的錯覺。(只要把NODE_ENV 改成 production,就能夠啓動生產環境構建模式了:https://facebook.github.io/react/downloads.html#npm.)

經過React.addons.Perf來識別性能問題

在深刻講解常見問題的修復前,重點強調一下,你必須只花時間來修復你所能把控的那些問題。若是你毫無約束地亂優化是很容易走進死衚衕的。囉嗦一下,應該專一於構建,而且只把時間花在修復主要的性能瓶頸上。

使用標準的調試工具來識別性能瓶頸仍然是可行的,可是常常很難來解釋數據,由於實際應用的代碼會比在React-land中的代碼花費更多的時間(例如:你寫的一個複雜的渲染方式運行得很快,可是其帶來的虛擬DOM計算倒是至關昂貴的)。這使咱們很難在React-land中識別哪些應用代碼致使了明顯的瓶頸問題。

幸運的是,React自帶一些性能檢測工具,能夠在React的非生產構建環境中使用(文檔)。經過react/addons,你能夠找到對應的React.addons.Perf

咱們能夠這樣寫:

<IntermediateBinder
  deleteItem={this.deleteItem}
  boundArg={item.id}>
  {(boundProps) => <TodoItem deleteItem={boundProps.deleteItem} />}
</IntermediateBinder>

(咱們探索的另外一個可能的作法是,使用一個自定義的綁定函數,這個函數自己儲存了元數據, 它和一個更高端的檢測函數結合使用,就能夠檢測到功能的結合實際上尚未改變。這彷佛不能知足咱們的需求。)

構造數組、對象字面量

這很簡單,只是常常被忽略了。數組字面量會破壞 PureRenderMixin:

> ['important', 'starred'] === ['important', 'starred']
false

若是你不但願這個對象被改變,你就能夠把它放到一個模塊常量或者組件靜態變量中:

`const TAGS = ['important', 'starred'];`

子組件

在一個組件和它的子組件之間定義內容界限有利於性能優化----接口封裝性良好的組件能夠天然地促進性能更新。重構中間的組件能夠幫助提升性能,你也可使用 PureRenderMixin 來保存更新。

<div>
  <ComplexForm props={this.props.complexFormProps} />
  <ul>
    <li prop={this.props.items[0]}>item A</li>
    ...1000 items...
  </ul>
</div>

在上面這個例子中,若是 complexFormPropsitems 來自同一個 store 的話,那麼在 complexFormProps 裏面輸入,就會引起 store 的更新,而每一個 store 的更新又會致使上面這整個實例的從新渲染。虛擬 DOM 的差別是很棒的,但仍然須要每次都檢測。 然而,重構它的子組件,採用 this.props.items,這樣就只有當 this.props.items 變化時纔會更新狀態。

<div>
  <CustomList items={this.props.items} />
  <ComplexForm props={this.props.complexFormProps} />
</div>

緩存昂貴的計算

這個跟 狀態來源單一性 原則有些相悖,可是若是 prop 中的計算是昂貴的,你就能夠把它緩存在組件中。咱們沒必要在渲染的方法中,直接地調用 doExpensiveComputation(this.prop.someProp) ,能夠把這個函數進行封裝,在prop 狀態沒改變的時候,把它緩存起來。

getCachedExpensiveComputation() {
  if (this._cachedSomeProp !== this.prop.someProp) {
    this._cachedSomeProp = this.prop.someProp;
    this._cachedComputation = doExpensiveComputation(this.prop.someProp);
  }
  return this._cachedComputation;
}

後續的優化人員使用JS分析器,將能夠很好地發現這個問題。

狀態連接

React 的雙向數據綁定對於簡單的控制反轉(IoC)很是有用,它容許子組件向父組件傳遞新的狀態。若是對React表單組件只是使用 valueLink 的話是沒那麼糟糕的,由於 React 的表單輸入是很簡單的。但若是你像咱們同樣,在多個組件之間串聯,那就會遇到問題了。狀態連接實施以下:

linkState(key) {
  return new ReactLink(
    this.state[key],
    ReactStateSetters.createStateKeySetter(this, key)
  );
}

儘管狀態沒有改變,每調用一次 linkState 都會返回一個新的對象!這意味着 shallowCompare 永遠不會起做用。不幸的是,咱們的變通方案就是乾脆不使用 linkState。 若是不是要把一個 linkState 變成一個 getter prop 和一個 setter prop 的話,咱們要避免建立一個新的對象。例如:nameLink={this.linkState(‘name')} 能夠被替換成 name={this.state.name} setName={this.setName}。(咱們已經考慮寫一個能夠對自身進行緩存的 linkState了)

編譯程序的優化

新版的 Bebel 和 React 支持內聯React元素而且自動提高常量。不幸的是,咱們尚未用過這方面的技術,但它們將有助於減小 React.createElement 的調用, 以及加速DOM的更新和解。

總結

剛剛咱們看了不少 (你應該看過原列表的!), 可是關鍵的兩點就是你要習慣 profilingshouldComponentUpdate。 我但願這些都可以幫到你!

任何建議、評論等,若是咱們錯過了,歡迎經過 benchling.com 讓咱們知悉。

請繼續關注本系列文章的第二篇,咱們將討論 React 的調試工做流,深刻存在性能問題的代碼實例,進而示範如何修復。

Hacker News的討論

更新: 第二篇已經出來啦! Check it out - A Deep Dive into React Perf Debugging.

咱們一如既往地歡迎喜歡咱們產品的朋友來加入這個團隊. :)

相關文章
相關標籤/搜索