編寫高性能React組件-傳值篇

不少人在寫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參數用於返回其餘數據

 

若是你們有什麼意見或者建議均可以在評論裏面提哦

相關文章
相關標籤/搜索