React 效能優化總結
初學者對React可能滿懷期待,覺得React可能完爆其它一切框架,甚至不切實際地認為React可能連原生的渲染都能完爆——對框架的狂熱確實會出現這樣的不切實際的期待。讓我們來看看React的官方是怎麼說的。React官方文件在Advanced Performanec這一節,這樣寫道:
One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version
顯然React自己也其實只是想盡量達到跟非React版本相若的效能,
你所不知道的render
react的元件渲染分為初始化渲染和更新渲染。在初始化渲染的時候會呼叫根元件下的所有元件的render方法進行渲染,如下圖(綠色表示已渲染,這一層是沒有問題的):
但是當我們要更新某個子元件的時候,如下圖的綠色元件(從根元件傳遞下來應用在綠色元件上的資料發生改變):
我們的理想狀態是隻呼叫關鍵路徑上元件的render,如下圖:
但是react的預設做法是呼叫所有元件的render,再對生成的虛擬DOM進行對比,如不變則不進行更新。這樣的render和虛擬DOM的對比
明顯是在浪費,如下圖(黃色表示浪費的render和虛擬DOM對比)
Tips:
- 拆分元件是有利於複用和元件優化的。
- 生成虛擬DOM並進行比對發生在render()後,而不是render()前。
更新階段的生命週期
componentWillReceiveProps(object nextProps)
:當掛載的元件接收到新的props時被呼叫。此方法應該被用於比較this.props 和 nextProps以用於使用this.setState()執行狀態轉換。(元件內部資料有變化,使用state,但是在更新階段又要在props改變的時候改變state,則在這個生命週期裡面)shouldComponentUpdate(object nextProps, object nextState)
: -boolean 當元件決定任何改變是否要更新到DOM時被呼叫。作為一個優化
實現比較this.props 和 nextProps 、this.state 和 nextState ,如果React應該跳過更新,返回false。componentWillUpdate(object nextProps, object nextState)
:在更新發生前被立即呼叫。你不能在此呼叫this.setState()
。componentDidUpdate(object prevProps, object prevState)
: 在更新發生後被立即呼叫。(可以在DOM更新完之後,做一些收尾的工作)
Tips:
- React的優化是基於
shouldComponentUpdate
的,該生命週期預設返回true,所以一旦prop或state有任何變化,都會引起重新render。
shouldComponentUpdate
react在每個元件生命週期更新的時候都會呼叫一個shouldComponentUpdate(nextProps, nextState)函式。它的職責就是返回true或false,true表示需要更新,false表示不需要,預設返回為true,即便你沒有顯示地定義 shouldComponentUpdate 函式。這就不難解釋上面發生的資源浪費了。
為了進一步說明問題,我們再引用一張官網的圖來解釋,如下圖( SCU表示shouldComponentUpdate,綠色表示返回true(需要更新),紅色表示返回false(不需要更新);vDOMEq表示虛擬DOM比對,綠色表示一致(不需要更新),紅色表示發生改變(需要更新)):
根據渲染流程,首先會判斷shouldComponentUpdate(SCU)是否需要更新。如果需要更新,則呼叫元件的render生成新的虛擬DOM,然後再與舊的虛擬DOM對比(vDOMEq),如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM;如果SCU不需要更新,則直接保持不變,同時其子元素也保持不變。
- C1根節點,綠色SCU (true),表示需要更新,然後vDOMEq紅色,表示虛擬DOM不一致,需要更新。
- C2節點,紅色SCU (false),表示不需要更新,所以C4,C5均不再進行檢查
- C3節點同C1,需要更新
- C6節點,綠色SCU (true),表示需要更新,然後vDOMEq紅色,表示虛擬DOM不一致,更新DOM。
- C7節點同C2
- C8節點,綠色SCU (true),表示需要更新,然後vDOMEq綠色,表示虛擬DOM一致,不更新DOM。
帶坑的寫法:
- {…this.props} (不要濫用,請只傳遞component需要的props,傳得太多,或者層次傳得太深,都會加重shouldComponentUpdate裡面的資料比較負擔,因此,也請慎用spread attributes(<Component {…props} />))。
- ::this.handleChange()。(請將方法的bind一律置於constructor)
- this.handleChange.bind(this,id)
- 複雜的頁面不要在一個元件裡面寫完。
- 請儘量使用const element。
- map裡面新增key,並且key不要使用index(可變的)。具體可參考使用Perf工具研究React Key對渲染的影響
- 儘量少用setTimeOut或不可控的refs、DOM操作。
- 資料儘可能簡單明瞭,扁平化。
效能檢測工具
React官方提供的:React.addons.Perf
react官方提供一個外掛React.addons.Perf
可以幫助我們分析元件的效能,以確定是否需要優化。
開啟console皮膚,先輸入Perf.start()
執行一些元件操作,引起資料變動,元件更新,然後輸入Perf.stop()
。(建議一次只執行一個操作,好進行分析)
再輸入Perf.printInclusive
檢視所有涉及到的元件render,如下圖(官方圖片):
或者輸入Perf.printWasted()檢視下不需要的的浪費元件render,如下圖(官方圖片):
優化前:
優化後:
其他的檢測工具
react-perf-tool為React應用提供了一種視覺化的效能檢測方案,該工程同樣是基於React.addons,但是使用圖表來顯示結果,更加方便。
React官方的解決方案
PureRenderMixin(es5)
var PureRenderMixin = require('react-addons-pure-render-mixin'); React.createClass({ mixins: [PureRenderMixin], render: function() { return <div className={this.props.className}>foo</div>; } });
Shallow Compare (es6)
var shallowCompare = require('react-addons-shallow-compare'); export class SampleComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } render() { return <div className={this.props.className}>foo</div>; } }
es7裝飾器的寫法:
import pureRender from "pure-render-decorator" ... @pureRender class Person extends Component { render() { console.log("我re-render了"); const {name,age} = this.props; return ( <div> <span>姓名:</span> <span>{name}</span> <span> age:</span> <span>{age}</span> </div> ) } }
pureRender很簡單,就是把傳進來的component的shouldComponentUpdate給重寫掉了,原來的shouldComponentUpdate,無論怎樣都是return ture,現在不了,我要用shallowCompare比一比,shallowCompare程式碼及其簡單,如下:
function shallowCompare(instance, nextProps, nextState) { return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState); }
缺點
shallowEqual其實只比較props的第一層子屬性是不是相同,就像上述程式碼,props 是如下
{ detail: { name: "123", age: "123" } }
他只會比較props.detail ===nextProps.detail,導致在傳入複雜的資料的情況下,優化失效。
immutable.js
我們也可以在 shouldComponentUpdate()
中使用使用 deepCopy 和 deepCompare 來避免無必要的 render(),但 deepCopy 和 deepCompare 一般都是非常耗效能的。
Immutable Data 就是一旦建立,就不能再被更改的資料。對 Immutable 物件的任何修改或新增刪除操作都會返回一個新的 Immutable 物件。
Immutable 實現的原理是 Persistent Data Structure
(持久化資料結構),也就是使用舊資料建立新資料時,要保證舊資料同時可用且不變。同時為了避免 deepCopy 把所有節點都複製一遍帶來的效能損耗,Immutable 使用了 Structural Sharing
(結構共享),即如果物件樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。請看下面動畫:
Immutable 則提供了簡潔高效的判斷資料是否變化的方法,只需 === 和 is 比較就能知道是否需要執行 render(),而這個操作幾乎 0 成本,所以可以極大提高效能。修改後的 shouldComponentUpdate
是這樣的:
import { is } from 'immutable'; shouldComponentUpdate: (nextProps = {}, nextState = {}) => { const thisProps = this.props || {}, thisState = this.state || {}; if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) { return true; } for (const key in nextProps) { if (!is(thisProps[key], nextProps[key])) { return true; } } for (const key in nextState) { if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) { return true; } } return false; }
react-immutable-render-mixin
這是一個facebook/immutable-js的react pure render mixin 的庫,可以簡化很多寫法。
使用react-immutable-render-mixin可以實現裝飾器的寫法。
import React from 'react'; import { immutableRenderDecorator } from 'react-immutable-render-mixin'; @immutableRenderDecorator class Test extends React.Component { render() { return <div></div>; } }
無狀態元件
為了避免一定程度的浪費,react官方還在0.14版本中加入了無狀態元件
,如下:
// es6 const HelloMessage = (props) => <div>Hello {props.name}</div>;
高階元件(接下來的方向)
大部分使用mixin和class extends的地方,高階元件都是更好的方案——畢竟
組合優於繼承
。
參考文章
使用ES6編寫React應用(4):使用高階元件替代Mixins
Mixin 已死,Composition 萬歲
React同構直出(接下來方向)
同構基於服務端渲染,卻不止是服務端渲染。
React在減少重複渲染方面確實是有一套獨特的處理辦法,那就是virtual DOM,但顯示在首次渲染的時候React絕無可能超越原生的速度。因此,我們在做優化的時候,接下來可以做的事情就是:
首屏時間可能會比較原生的慢一些,但可以嘗試用React Server Render (又稱Isomorphic)去提高效率
相關文章
- React效能優化總結React優化
- web前端培訓React效能優化總結Web前端React優化
- 前端效能優化總結前端優化
- iOS 效能優化總結iOS優化
- canvas效能優化總結Canvas優化
- Android效能優化——效能優化的難題總結Android優化
- App瘦身、效能優化總結APP優化
- 小程式效能優化總結優化
- 系統效能優化總結優化
- react效能優化React優化
- React 效能優化React優化
- PHP的效能優化方法總結PHP優化
- UI技術總結--效能優化UI優化
- 總結前端效能優化的方法前端優化
- 打個總結:Web效能優化Web優化
- ⚠️Flutter 效能優化實踐 總結⚠️Flutter優化
- vuejs專案效能優化總結VueJS優化
- MySQL的SQL效能優化總結MySql優化
- mysql查詢效能優化總結MySql優化
- react/react-native效能優化React優化
- react-效能優化React優化
- React渲染效能優化React優化
- React+Webpack效能優化ReactWeb優化
- Android效能優化來龍去脈總結Android優化
- React Native 效能優化元件-PureComponentReact Native優化元件
- React效能優化方案之PureRenderMixinReact優化
- React效能優化方案之PureComponentReact優化
- 4、React元件之效能優化React元件優化
- Java 效能優化之——效能優化的過程方法與求職面經總結Java優化求職
- android常見的效能優化方面的總結Android優化
- Hive常用效能優化方法實踐全面總結Hive優化
- 從 React render 談談效能優化React優化
- React 16 載入效能優化指南React優化
- React 效能優化 - 避免重複渲染React優化
- React Native如何做效能優化React Native優化
- Linux效能優化實戰CPU篇之總結(四)Linux優化
- 記一次介面效能優化實踐總結:優化介面效能的八個建議優化
- vue效能優化小結Vue優化