不少人在寫React組件的時候沒有太在乎React組件的性能,使得React作了不少沒必要要的render,如今我就說說該怎麼來編寫搞性能的React組件。javascript
首先咱們來看一下下面兩個組件html
import React, {PureComponent,Component} from "react" import PropTypes from "prop-types" class A extends Component { constructor(props){ super(props); } componentDidUpdate() { console.log("componentDidUpdate") } render (){ return ( <div /> ) } } class Test extends Component { constructor(props) { super(props); this.state={ value:0 }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(()=>{ this.setState({value:this.state.value+1}) },100); } render() { return ( <A /> ) } }
運行結果:java
Test state change. A componentDidUpdate
咱們發現上面代碼中只要執行了Test組件的中的setState,不管Test組件裏面包含的子組件A是否須要這個state裏面的值,A componentDidUpdate始終會輸出node
試想下若是子組件下面還有不少子組件,組件又嵌套子組件,子子孫孫無窮盡也,這是否是個很可怕的性能消耗?react
固然,針對這樣的一個問題最初的解決方案是經過shouldComponentUpdate方法作判斷更新,咱們來改寫下組件Asegmentfault
class A extends Component { constructor(props){ super(props); } static propTypes = { value:PropTypes.number }; static defaultProps = { value:0 }; shouldComponentUpdate(nextProps, nextState) { return nextProps.value !== this.props.value; } componentDidUpdate() { console.log("A componentDidUpdate"); } render (){ return ( <div /> ) } }
這裏增長了shouldComponentUpdate方法來對傳入的value屬性進行對面,雖然這裏沒有傳,可是不影響,運行結果:函數
Test state change.
好了,此次結果就是咱們所須要的了,可是若是每個組件都這樣作一次判斷是否太過於麻煩?性能
那麼React 15.3.1版本中增長了 PureComponent ,咱們來改寫一下A組件優化
class A extends PureComponent { constructor(props){ super(props); } static propTypes = { value:PropTypes.number }; static defaultProps = { value:0 }; componentDidUpdate() { console.log("A componentDidUpdate"); } render (){ return ( <div /> ) } }
此次咱們去掉了shouldComponentUpdate,繼承基類咱們改爲了PureComponent,輸出結果:this
Test state change.
很好,達到了咱們想要的效果,並且代碼量也減少了,可是真的能夠作到徹底的防止組件無畏的render嗎?讓咱們來看看PureComponent的實現原理
最重要的代碼在下面的文件裏面,固然這個是React 16.2.0版本的引用
/node_modules/fbjs/libs/shallowEqual
大體的比較步驟是:
1.比較兩個Obj對象是否徹底相等用===判斷
2.判斷兩個Obj的鍵數量是否一致
3.判斷具體的每一個值是否一致
不過大家發現沒有,他只是比對了第一層次的結構,若是對於再多層級的結構的話就會有很大的問題
來讓咱們修改源代碼再來嘗試:
class A extends PureComponent { constructor(props){ super(props); } static propTypes = { value:PropTypes.number, obj:PropTypes.object }; static defaultProps = { value:0, obj:{} }; componentDidUpdate() { console.log("A componentDidUpdate"); } render (){ return ( <div /> ) } } class Test extends Component { constructor(props) { super(props); this.state={ value:0, obj:{a:{b:123}} }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(()=>{ console.log("Test state change."); let {obj,value} = this.state; //這裏修改了裏面a.b的值 obj.a.b=456; this.setState({ value:value+1, obj:obj }) },100); } render() { let { state } = this; let { value, obj } = state; return ( <A obj={obj} /> ) } }
輸出結果:
Test state change.
這裏難以想象吧!這也是不少人對引用類型理解理解不深刻所形成的,對於引用類型來講可能出現引用變了可是值沒有變,值變了可是引用沒有變,固然這裏就暫時不去討論js的數據可變性問題,要否則又是一大堆,你們可自行百度這些
那麼怎麼樣作才能真正的處理這樣的問題呢?我先增長一個基類:
import React ,{Component} from 'react'; import {is} from 'immutable'; class BaseComponent extends Component { constructor(props, context, updater) { super(props, context, updater); } shouldComponentUpdate(nextProps, nextState) { const thisProps = this.props || {}; const thisState = this.state || {}; nextState = nextState || {}; nextProps = nextProps || {}; 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 (!is(thisState[key], nextState[key])) { return true; } } return false; } } export default BaseComponent
你們可能看到了一個新的東西Immutable,不瞭解的能夠自行百度或者 Immutable 經常使用API簡介 , Immutable 詳解
咱們來改寫以前的代碼:
import React, {PureComponent,Component} from "react" import PropTypes from "prop-types" import Immutable from "immutable" import BaseComponent from "./BaseComponent" class A extends BaseComponent { constructor(props){ super(props); } static propTypes = { value:PropTypes.number, obj:PropTypes.object }; static defaultProps = { value:0, obj:{} }; componentDidUpdate() { console.log("A componentDidUpdate"); } render (){ return ( <div /> ) } } class Test extends Component { constructor(props) { super(props); this.state={ value:0, obj:Immutable.fromJS({a:{b:123}}) }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(()=>{ console.log("Test state change."); let {obj,value} = this.state; //注意,寫法不同了 obj = obj.setIn(["a","b"],456); this.setState({ value:value+1, obj:obj }) },100); } render() { let { state } = this; let { value, obj } = state; return ( <A obj={obj} /> ) } }
執行結果:
Test state change. A componentDidUpdate
這樣也達到了咱們想要的效果
固然,還有一種比較粗暴的辦法就是直接把obj換成一個新的對象也一樣能夠達到跟新的效果,可是可控性不大,並且操做不當的話也會致使過多的render,因此仍是推薦使用immutable對結構層級比較深的props進行管理
上面的一大堆主要是講述了對基本類型以及Object(Array 其實也是Object,這裏就不單獨寫示例了)類型傳值的優化,下面咱們來說述關於function的傳值
function其實也是Object,可是純的function比較特麼,他沒有鍵值對,沒法經過上面提供的方法去比對兩個function是否一致,只有經過引用去比較,因此改不改引用成爲了關鍵
改了下代碼:
import React, {PureComponent,Component} from "react" import PropTypes from "prop-types" import Immutable from "immutable" import BaseComponent from "./BaseComponent" class A extends BaseComponent { constructor(props){ super(props); } static propTypes = { value:PropTypes.number, obj:PropTypes.object, onClick:PropTypes.func }; static defaultProps = { value:0, obj:{} }; componentDidUpdate() { console.log("A componentDidUpdate"); } render (){ let { onClick } = this.props; return ( <div onClick={onClick} > 你來點擊試試!!! </div> ) } } class Test extends Component { constructor(props) { super(props); this.state={ value:0, }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(()=>{ console.log("Test state change."); let {value} = this.state; this.setState({ value:value+1, }) },100); } onClick(){ alert("你點擊了一下!") } render() { let { state } = this; let { value, obj } = state; return ( <A onClick={()=>this.onClick()} /> ) } }
運行結果:
Test state change. A componentDidUpdate
咱們setState之後控件A也跟着更新了,並且還用了咱們上面所用到的BaseComponent,難道是BaseComponent有問題?其實並非,看Test組件裏面A的onClick的賦值,這是一個匿名函數,這就意味着其實每次傳入的值都是一個新的引用,必然會致使A的更新,咱們這樣幹:
class Test extends Component { constructor(props) { super(props); this.state={ value:0, }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(()=>{ console.log("Test state change."); let {value} = this.state; this.setState({ value:value+1, }) },100); } onClick=()=>{ alert("你點擊了一下!") }; render() { let { state } = this; let { value } = state; return ( <A onClick={this.onClick} /> ) } }
輸出結果:
Test state change.
嗯,達到咱們想要的效果了,完美!
不過我仍是發現有個問題,若是我在事件或者回調中須要傳值就痛苦了,因此在寫每一個組件的時候,若是有事件調用或者回調的話最好定義一個接收任何類型的屬性,最終的代碼相似下面這樣
import React, {PureComponent, Component} from "react" import PropTypes from "prop-types" import Immutable from "immutable" import BaseComponent from "./BaseComponent" class A extends BaseComponent { constructor(props) { super(props); } static propTypes = { value: PropTypes.number, obj: PropTypes.object, onClick: PropTypes.func, //增長data傳值,接收任何類型的參數 data: PropTypes.any }; static defaultProps = { value: 0, obj: {}, data: "" }; componentDidUpdate() { console.log("A componentDidUpdate"); } //這裏也進行了一些修改 onClick = () => { let { onClick, data } = this.props; onClick && onClick(data); }; render() { return ( <div onClick={this.onClick}> 你來點擊試試!!! </div> ) } } class Test extends Component { constructor(props) { super(props); this.state = { value: 0, }; } static propTypes = {}; static defaultProps = {}; componentDidMount() { setTimeout(() => { console.log("Test state change."); let {value} = this.state; this.setState({ value: value + 1, }) }, 100); } onClick = () => { alert("你點擊了一下!") }; render() { let { state } = this; let { value } = state; return ( <A onClick={this.onClick} data={{message: "任何我想傳的東西"}} /> ) } }
總結一下:
1.編寫React組件的時候使用自定義組件基類做爲其餘組件的繼承類
2.使用Immutable管理複雜的引用類型狀態
3.傳入function類型的時候要傳帶引用的,而且注意預留data參數用於返回其餘數據
若是你們有什麼意見或者建議均可以在評論裏面提哦