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節點更新、插入、替換時,它都會閃現一個顏色。然而,元素面板的閃現,或者說是從新渲染也將影響到性能!常常我會從元素窗口切換到控制檯,來更準確地感知每秒的幀數。
在用進行React開發時,當一個組件被渲染時,常常要進行PropType 校驗。組件所接收到的 prop
先被檢測來幫助調試和開發。使用 Chrome 提供的 JS Profiler
,你能夠發現React組件在這個校驗的方法上花費了很長一段時間。
儘管開發環境的警告提示有助於調試,但它們是會有一些性能方面的代價的,這些代價則不會反映在生產環境。有時我會使用切換到生產構建環境來忽略這種遲緩的錯覺。(只要把NODE_ENV
改成 production
,就能夠啓動生產環境構建模式了:https://facebook.github.io/react/downloads.html#npm.)
在深刻講解常見問題的修復前,重點強調一下,你必須只花時間來修復你所能把控的那些問題。若是你毫無約束地亂優化是很容易走進死衚衕的。囉嗦一下,應該專一於構建,而且只把時間花在修復主要的性能瓶頸上。
使用標準的調試工具來識別性能瓶頸仍然是可行的,可是常常很難來解釋數據,由於實際應用的代碼會比在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>
在上面這個例子中,若是 complexFormProps
和 items
來自同一個 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的更新和解。
剛剛咱們看了不少 (你應該看過原列表的!), 可是關鍵的兩點就是你要習慣 profiling
和 shouldComponentUpdate
。 我但願這些都可以幫到你!
任何建議、評論等,若是咱們錯過了,歡迎經過 benchling.com 讓咱們知悉。
請繼續關注本系列文章的第二篇,咱們將討論 React 的調試工做流,深刻存在性能問題的代碼實例,進而示範如何修復。
更新: 第二篇已經出來啦! Check it out - A Deep Dive into React Perf Debugging.
咱們一如既往地歡迎喜歡咱們產品的朋友來加入這個團隊. :)