React.PureComponent 和React.Component的区别

什么是PureComponent?

PureComponent是一个继承了Component的子类,它会自动加载shouldComponentUpdate函数。
当组件有更新的时候,shouldComponentUpdate函数会对组件的stateprops进行一次浅比较,
如果propsstate都没有发生变化,就不会执行render函数,在react性能方面得到了优化。

所以PureComponent的原理也是通过执行浅比较优化组件性能。

PureComponent解决了什么问题?

PureComponent用浅比较的方法解决了当state或者props在不变的情况下重复调用render函数导致性能下降的问题。

PureComponentComponent 的区别是什么?

(1)PureComponent会自动执行shouldComponentUpdate方法。通过shallowEqual的浅对比实现组件的性能优化。而Component要自己调用shouldComponentUpdate来实现组件的优化。

(2)PureComponent不仅会影响自身,还会影响子组件。所以使用PureComponent的最佳情况是展示组件。

a:父组件和子组件都是继承自Component,只要有变化时,都会进行更新。

b:父组件和子组件都是继承自PureComponent,是否更新依赖各自的props、state

c:父组件继承PureComponent,子组件继承Component,若父组件的stateprops没有变化,而子组件的有变化。子组件不会触发更新,因为父组件没有更新,子组件也受其影响。

d:父组件继承Component,子组件继承PureComponent,那看各自的props、state

(3)若是引用类型(数组或对象),需要其引用的地址发生改变才会重新渲染。

(4)若propsstate每次都会变化,建议还是用Component,因为PureComponent的浅比较也要花费时间。

(5)若有shouldComponentUpdate方法,则会执行它,若没有,判断是否为PureComponent组件,若是,进行浅比较。

PureComponent的优缺点:

优点:

不需要开发者使用shouldComponentUpdate来进行判断提升性能,它会自己进行浅比较。

缺点

对于引用类型的props或者state,在判断时可能会出问题导致不会触发渲染。

当为基本类型时对比PureComponentComponent

看个例子:

import React, { Component, PureComponent } from 'react';

class DiffComponent extends Component {
    render() {
        return (
            <div>
                比较PureComponent和Component
                <div style={{ display: 'flex' }}>
                    <div style={{ marginRight: 200 }}>
                        <PureComp />
                    </div>
                    <NormalComp />
                </div>
            </div>
        );
    }
}

export default DiffComponent;

class PureComp extends PureComponent {
    constructor() {
        super();
        this.state = { 
            text: 'true', // 值引用
        };
        console.log('pure-constructor');
    }

    changeState = () => {
        this.setState({ 
            text: 'false',
        });
    };

    render() {
        console.log('pure-render',this);
        return (
            <div>
                PureComponent
                <button onClick={this.changeState}>Click</button>
                <div>{this.state.text}</div>
            </div>
        );
    }
}

class NormalComp extends Component {
    constructor() {
        super();
        this.state = { 
            text: 'true', // 值引用
        };
        console.log('normal-constructor');
    }

    changeState = () => {
        this.setState({ 
            text: 'false',
        });
    };

    render() {
        console.log('normal-render',this);
        return (
            <div>
                normalComponent
                <button onClick={this.changeState}>Click</button>
                <div>{this.state.text}</div>
            </div>
        );
    }
}

复制代码

如上所示,PureComp继承自PureComponentNormalComp继承自Component,这两个组件都设置了一个statetext
通过点击按钮可以改变text的值,将text设置为false

对比分析:

(1)组件初始化时:

PureComp初始化时,依次输入pure-constructor,pure-render

NormalComp初始化时,依次输入normal-constructor,normal-render

(2)当点击按钮时:

PureCom组件:

第一次点击按钮,界面的值被改为false,执行render方法,输出pure-render

后面再多次点击按钮发现,不会再输出pure-render,说明不会在执行render方法了。

NormalComp组件:

第一次点击按钮,界面的值被改为false,执行render方法,输出normal-render

后面再多次点击按钮发现,每次点击按钮都会输出normal-render。说明每次都会执行render方法。

总结:

这就是PureComponent内部优化的体现,PureComponentstateprops做了浅比较。

而对于NormalComp组件而言,继承的Component,只要当state发生改变都会执行render方法。

若想优化判断不要多次执行render,可以在shouldComponentUpdate生命周期中进行比较。

返回true就会重新渲染执行render,返回false则不会重新渲染。

同理也适用于stringnumber等基本类型的数据

当为引用类型时对比PureComponentComponent

还是上面的例子,只是我们在state中加入arr,并且在render方法中渲染出arr的值。

import React, { Component, PureComponent } from 'react';

class DiffComponent extends Component {
    render() {
        return (
            <div>
                比较PureComponent和Component
                <div style={{ display: 'flex' }}>
                    <div style={{ marginRight: 200 }}>
                        <PureComp />
                    </div>
                    <NormalComp />
                </div>
            </div>
        );
    }
}

export default DiffComponent;

class PureComp extends PureComponent {
    constructor() {
        super();
        this.state = { 
            arr: [1], // 类型引用
        };
        console.log('pure-constructor');
    }

    changeState = () => {
        let {arr} = this.state;
        arr.push('2');
        this.setState({ 
            arr
        });
    };

    render() {
        console.log('pure-render',this.state);
        return (
            <div>
                PureComponent
                <button onClick={this.changeState}>Click</button>
                {
                    this.state.arr.map(item=>{
                        return <div>{item}</div>
                    })
                }
            </div>
        );
    }
}

class NormalComp extends Component {
    constructor() {
        super();
        this.state = { 
            arr: [1], // 类型引用
        };
        console.log('normal-constructor');
    }

    changeState = () => {
        let {arr} = this.state;
        arr.push('2');
        this.setState({ 
            arr
        });
    };


    render() {
        console.log('normal-render',this.state);
        return (
            <div>
                normalComponent
                <button onClick={this.changeState}>Click</button>
                {
                    this.state.arr.map(item=>{
                        return <div>{item}</div>
                    })
                }
            </div>
        );
    }
}
复制代码

对比分析:

(1)组件初始化时都依次执行constructrenderpureComponentComponent没啥区别。

(2)当点击按钮时:

PureComp组件:

点击按钮时,控制台也未输出任何东西,没有执行render方法。

NormalComp组件:

每次点击按钮,控制台都会输出normal-render。界面的值也会改变,说明每次都会执行render方法。

总结:

PureComp组件未重新渲染执行render,是因为浅对比时发现state中的arr引用类型没有发生改变,所以不会执行render

若将changeState修改为如下写法,改变arr的引用,则PureComponent会执行render方法。

    changeState = () => {
        let newArr = [...this.state.arr];
        newArr.push('2');
        this.setState({ 
            arr: newArr
        });
    };
复制代码

PureComponent源码分析

PureComponent源码:


export default function PureComponent (props, context) {
    Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototye)
PureComponent.prototype.contructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare
 
function shallowCompare (nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}

复制代码

从上面的源码可以看出,PureComponent继承了Component,并且重写了shouldComponentUpdate的方法。
shouldComponentUpdate只是返回了shallowEqual方法的返回值。

下面来看看shallowEqual方法的源码

function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }
 
  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }
 
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
 
  if (keysA.length !== keysB.length) {
    return false;
  }
 
  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }
 
  return true;
}
复制代码

shallowEqual函数做的事情如下:

(1)通过is函数对两个参数进行比较,判断是否相同,相同直接返回true:基本数据类型值相同,同一个引用对象都表示相同

(2)如果两个参数不相同,判断两个参数是否至少有一个不是引用类型,存在即返回false,如果两个都是引用类型对象,则继续下面的比较;

(3)判断两个不同引用类型对象是否相同

先通过Object.keys获取到两个对象的所有属性,具有相同属性,且每个属性值相同即两个对相同(相同也通过is函数完成)

is方法实现如下

function is(x, y) {
  // SameValue algorithm
  if (x === y) {
    // 排除 +0 === -0的情况
    // x !== 0 || y !== 0 可以确定x,y 都为 0 或者 -0
    // 1 / x === 1 / y 可以确定x,y的正负号不同,因为 Infinity !== -Infinity 是成立的
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // x !== x 可以确定 x是不是NaN,y同理,如果x,y都为NaN,返回true
    return x !== x && y !== y;
  }
}
复制代码

总结

简单总结一下,PureComponent就是一个继承了Component的子类并且会自执行shouldComponentUpdate方法进行shallowEqual浅比较,以此来解决组件的性能问题。

下次我们再简单实现一个PureComponent

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享