初學者對React可能滿懷期待,以爲React可能完爆其它一切框架,甚至不切實際地認爲React可能連原生的渲染都能完爆——對框架的狂熱確實會出現這樣的不切實際的期待。讓咱們來看看React的官方是怎麼說的。React官方文檔在Advanced Performanec這一節,這樣寫道:html
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 versionreact
顯然React本身也其實只是想盡可能達到跟非React版本至關的性能。git
react的組件渲染分爲初始化渲染和更新渲染。
在初始化渲染的時候會調用根組件下的全部組件的render方法進行渲染,以下圖(綠色表示已渲染,這一層是沒有問題的):es6
可是當咱們要更新某個子組件的時候,以下圖的綠色組件(從根組件傳遞下來應用在綠色組件上的數據發生改變):github
咱們的理想狀態是隻調用關鍵路徑上組件的render,以下圖:web
可是react的默認作法是調用全部組件的render
,再對生成的虛擬DOM進行對比
,如不變則不進行更新。這樣的render和虛擬DOM的對比明顯是在浪費,以下圖(黃色表示浪費的render和虛擬DOM對比)segmentfault
Tips:性能優化
拆分組件是有利於複用和組件優化的。bash
生成虛擬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。
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操做。
props
和state
的數據儘量簡單明瞭,扁平化。
使用return null
而不是CSS的display:none
來控制節點的顯示隱藏。保證同一時間頁面的DOM節點儘量的少。
react官方提供一個插件React.addons.Perf
能夠幫助咱們分析組件的性能,以肯定是否須要優化。
打開console面板,先輸入Perf.start()
執行一些組件操做,引發數據變更,組件更新,而後輸入Perf.stop()
。(建議一次只執行一個操做,好進行分析)
再輸入Perf.printInclusive
查看全部涉及到的組件render,以下圖(官方圖片):
或者輸入Perf.printWasted()查看下不須要的的浪費組件render,以下圖(官方圖片):
優化前:
優化後:
react-perf-tool爲React應用提供了一種可視化的性能檢測方案,該工程一樣是基於React.addons,可是使用圖表來顯示結果,更加方便。
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});複製代碼
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
,致使在傳入複雜的數據的狀況下,優化失效。
React在15.3.0
裏面加入了了React.PureComponent
- 一個可繼承的新的基礎類, 用來替換react-addons-pure-render-mixin
。用法:
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>
);
}
}複製代碼
在ES6裏面寫起來簡直爽歪歪,惋惜同樣只支持淺比較。
咱們也能夠在 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;
}複製代碼
這是一個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>;
}
}複製代碼
這裏可參考個人另外一篇blog:使用immutable優化React
爲了不必定程度的浪費,react官方還在0.14版本中加入了無狀態組件
,
這種組件沒有狀態,沒有生命週期,只是簡單的接受 props 渲染生成 DOM 結構。無狀態組件很是簡單,開銷很低,若是可能的話儘可能使用無狀態組件。好比使用箭頭函數定義:
// es6
const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);複製代碼
由於無狀態組件只是函數,因此它沒有實例返回,這點在想用 refs 獲取無狀態組件的時候要注意,參見DOM 操做。
大部分使用mixin和class extends的地方,高階組件都是更好的方案——畢竟
組合優於繼承
。
使用ES6編寫React應用(4):使用高階組件替代Mixins
Mixin 已死,Composition 萬歲
同構基於服務端渲染,卻不止是服務端渲染。
React在減小重複渲染方面確實是有一套獨特的處理辦法,那就是virtual DOM,但顯示在首次渲染的時候React絕無可能超越原生的速度。所以,咱們在作優化的時候,接下來能夠作的事情就是:
首屏時間可能會比較原生的慢一些,但能夠嘗試用React Server Render (又稱Isomorphic)去提升效率
React同構直出優化總結
騰訊新聞React同構直出優化實踐