如何正确使用PureComponent做性能优化

1,656 阅读4分钟

什么是PureComponent

PureComponentReact使用类组件时做性能优化继承的一个组件,与Component拥有相同的生命周期方法,用法也一模一样,不同的是PureComponent重写了shouldComponentUpdate函数,对组件的stateprops做了一个浅比较,也就是说简单的比较了第一层数据是否相同,如果相同的话就返回falserender函数就不会再执行,从而提升性能,源码如下:

if (this._compositeType === CompositeTypes.PureClass) {
 shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}

其中, shadowEqual只会"浅"检查组件的 propsstate ,这就意味着嵌套对象和数组是不会被比较的。虽然使用PureComponent会带来性能提升,但不是所有组件都适用,如果使用不当,非但性能得不到提升,反而会降低。

什么情况下不适用

1.刷新数据列表时

// 父组件
export default Items extends React.Component {
    state = {
        items: []    
    }
    
    componentDidMount() {
        this.getItems()
    }
    
    getItems() {
        this.props.dispatch({
            type: 'getItems',
            payload: {...}
        }).then(data => {
            this.setState({ items: data })
        })
    }
    
    render() {
        return (
            <div>
            <Button onClick={this.getItems}>刷新列表<Button>
            {
                this.state.items.map(item => <Item item={item} />)        
            }
            </div>
        )
    }
}

// 子组件,注意这里继承了PureComponent
export default Item extends React.PureComponent {
    render() {
        return (
            <div>{this.props.item.name}</div>
        )
    }
}

简单解析下代码,父组件Items继承了React.Component,并提供一个按钮可以点击刷新数据,子组件Item继承了React.PureComponent,希望在刷新数据的时候做一个比较,数据不变的话就不重新渲染,但由于每次请求回来的数据都是新的,虽然数据不变,但是对象的引用改变了。此时做浅比较就没有意义了,完全是在浪费时间,还不如直接继承Component

2.父组件中使用PureComponent

// 父组件, 注意这里继承了PureComponent
export default Items extends React.PureComponent {
    state = {
        items: [
            {key: 'dsl', name: 'dsl'}
        ]    
    }
    
    
    addItem() {
        const { items } = this.state;
        items.push({ {key: 'dsl1', name: 'dsl1'}})
        this.setState({ items })
    }
    
    render() {
        return (
            <div>
            <Button onClick={this.addItem}>新增数据<Button>
            {
                this.state.items.map(item => <Item item={item} />)        
            }
            </div>
        )
    }
}

// 子组件
export default Item extends React.PureComponent {
    render() {
        return (
            <div>{this.props.item.name}</div>
        )
    }
}

父组件中继承了React.PureComponent,并且提供一个按钮,功能是点一下新增1条数据,希望的效果是最终页面上会渲染新增的数据,但最终结果是点击了页面没什么反应,还是只有1条数据。原因是父组件在做浅比较时由于items的引用没有改变,执行shouldComponentUpdate时返回了false,因此子组件不会在重新渲染,造成了Bug

什么情况下适用

上面是我想到不适用的2种情况,其他情况暂时没有想到,欢迎大佬补充,下面介绍适用的情况。

1.点击一个按钮,希望能够控制子组件展示

// 父组件,注意这里继承了PureComponent
export default Items extends React.PureComponent {
    state = {
        isShow: false
    }
    
    showItem() {
        this.setState({ isShow: true })
    }
    
    render() {
        return (
            <div>
            <Button onClick={this.showItem}>显示子组件<Button>
            {
                this.state.isShow && <Item />
            }
            </div>
        )
    }
}

// 子组件
export default Item extends React.Component {
    render() {
        return (
            <div>我是子组件,我展示了</div>
        )
    }
}

父组件继承了React.PureComponent,并且新增一个按钮,希望点击按钮时子组件能够展示,效果是子组件展示了。第一次点击按钮时,由于isShowfalse,点击按钮后isShow变为了true,这时由于继承了PureComponent,浅比较发现前后的isShow发生了变化,shouldComponentUpdate方法返回了true,子组件得到了渲染。第二次点击按钮时,子组件不会再渲染,因为isShow做了浅比较后发现没有改变,从而性能得到了提升。如果父组件使用的是Component,每点击一次就要子组件就要重新渲染一次。

2.子组件接收的数据是非引用类型

// 父组件
export default Items extends React.Component {
    state = {
        items: []    
    }
    
    componentDidMount() {
        this.getItems()
    }
    
    getItems() {
        this.props.dispatch({
            type: 'getItems',
            payload: {...}
        }).then(data => {
            this.setState({ items: data })
        })
    }
    
    render() {
        return (
            <div>
            <Button onClick={this.getItems}>刷新列表<Button>
            {
                // 注意,这里传递的是非引用类型
                this.state.items.map(item => <Item name={item.name} />)        
            }
            </div>
        )
    }
}

// 子组件,注意这里继承了PureComponent
export default Item extends React.PureComponent {
    render() {
        return (
            <div>{this.props.name}</div>
        )
    }
}

同样是父组件Items继承了React.Component,并提供一个按钮可以点击刷新数据,子组件Item继承了React.PureComponent,希望在刷新数据的时候做一个比较,数据不变的话就不重新渲染。不同的是父组件传递的是一个非引用类型的数据name,点击按钮刷新列表,做了浅比较后发现name没有改变,因此不会重新渲染。

总结

PureComponent给我的感觉是使用场景十分有限,特别是针对引用类型的数据不会带来什么好处,反而是白白做了一次比较消耗性能,在平时开发中最好不要滥用,在使用是时最好思考一下是否能够带来性能提升,欢迎大家在评论中补充其他的适用及不适用的情况。