【React】对比ShouldComponentUpdate、PureComponent和Memo

网上对比文章已经有好多了,不过主要是写给自己看的。

知识大纲

假设有这么一个页面, 父组件有一个子组件,子组件就一个纯div标签,父组件点击改变自己的state.count

import React from 'react';

class Children extends React.Component{
  render(){
    console.log('Children Update');
    return (
    	<div>Children Component</div>
    )
  }
}

class App extends React.Component{
  state = {
    count: 0
  }

  add = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  render(){
    const { count } = this.state;
    return (
      <div>
        <button onClick={this.add}>加一</button>
        <Children />
        <p>count: { count }</p>
      </div>
    )
  }
}

export default App;

在这里插入图片描述

如上,每次点击按钮,即使跟count没有关联的Children组件也会重新渲染,很容易影响性能。我们希望每次更新count时,跟count无关的组件不会重新渲染。这时候我们可以使用shoundComponentUpdate

ShouldComponentUpdate

shouldComponentUpdate(newProps, nextState){}

shouldComponentUpdate接受两个参数,一个是即将更新的props和一个即将更新的state。当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。

如果返回值是true就重新渲染,如果false就不做任何处理(会调用 UNSAFE_componentWillUpdate()render()componentDidUpdate()),使性能得到优化。

在这里我们对比未更新前的props.count和即将更新的props.count值,如果一样的话就不更新,如果不一样就刷新子组件:

shouldComponentUpdate(newProps, nextState){
    if(newProps.count === this.props.count) return false;
    return true;
}

在这里插入图片描述

这样的话,即使多次触发按钮,也不会多次渲染子组件了。

再举一个例子:

在这里插入图片描述

页面上有一个按钮,点击之后,重新赋值count,虽然count的值一直是0,但是这个组件还是一直再不停的渲染。

总结:当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化

接下来说一下缺点, 更改一下 内容:

import React from 'react';

class Children extends React.Component{
  shouldComponentUpdate(nextProps, nextState){
    console.log(nextProps.person.name, this.props.person.name);
    if(nextProps.person.name === this.props.person.name) return false;
    return true;
  }

  render(){
    console.log('Chidlred update');
    const { person, handleClick } = this.props;
    return (
    <div onClick={() => handleClick()}>Children Component - {person.name}</div>
    )
  }
}

class App extends React.Component{
  state = {
    person: {
      name: 'sugarMei'
    }
  }

  changeName = () => {
    const person = this.state.person;
    person.name = '王花花';
    this.setState({
      person: person
    })
  }


  render(){
    const { person } = this.state;
    return (
      <Children handleClick={this.changeName} person={person}  />
    )
  }
}

export default App;

虽然每次点击之后,界面上person.name没有发生改变。

在这里插入图片描述

这是由于person是一个对象,person其实是指向内存的指针,在shouldComponentUpdate中使用console.log(nextProps.person.name, this.props.person.name);;不管是nextProps.person还是this.props.person都是指向了同一个内存,结果是一样的。

console.log(nextProps.person.name, this.props.person.name); // 王花花 王花花

使用浅拷贝(官方推荐Object.assign)来改变name值,也是不行的:

changeName = () => {
    this.setState({
      person: Object.assign(this.state.person, { name: '王花花'})
    })
  }

故,如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果。

这里的解决方案有几种:

  • 使用深拷贝:

    changeName = () => {
        const person = JSON.parse(JSON.stringify(this.state.person));
        person.name = '王花花';
        this.setState({
          person
        })
     }
    

    我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

  • 使用第三方库immutable.js: 深拷贝/浅拷贝本身是很耗内存, 而immutable本身有一套机制使内存消耗降到最低,但是需要花点时间去学习API

最后,使用shouldCompoent可以便于我们去浅层比较一些数据之后然后来控制是否重新渲染,比较灵活,缺点时如果数据过多时,比较的逻辑可能会很长很复杂,以及对引用型数据的不太友好。

PureComponent

pureComponent也是浅层比较,不同于shouldComponentUpdate的是:

  • Component中可以使用shouldComponentUpdate,而在pureComponent中不行;
  • 如果使用shouldComponentUpdate需要自己手写逻辑,但是在pureComponent中就不用。

之前的例子:

import React from 'react';

class Children extends React.Component{
  shouldComponentUpdate(newProps, nextState){
    if(newProps.count === this.props.count) return false;
    return true;
  }

  render(){
    console.log('Children Update');
    return (
    	<div>Children Component</div>
    )
  }
}

class App extends React.Component{
  state = {
    count: 0
  }

  add = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  render(){
    const { count } = this.state;
    return (
      <div>
        <button onClick={this.add}>加一</button>
        <Children />
        <p>count: { count }</p>
      </div>
    )
  }
}

export default App;

我们修改Children组件,继承pureComponent

class Children extends PureComponent{
  render(){
    console.log('Children Update');
    return (
    	<div>Children Component</div>
    )
  }
}

这样也能确保得到同样的效果。

PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。

如果pureCompent包裹的组件中也希望引用类型数据改变的时候也会更新,那么可以借助forceUpdate

import React, { PureComponent } from 'react';

class Children extends PureComponent{
  render(){
    console.log('Children Update', this.props.person);
    return (
    	<div>Children Component, {this.props.person.name}</div>
    )
  }
}

class App extends React.Component{
  state = {
    count: 0,
    person: {
      name: 'sugar'
    }
  }

  add = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  changeName = () => {
    this.setState({
      person: Object.assign(this.state.person,  { name: '王花花'})
    })
    this.child.forceUpdate();
  }

  render(){
    const { count, person } = this.state;
    return (
      <div>
        <button onClick={this.add}>加一</button>
        <button onClick={this.changeName}>改变名字</button>
        <Children person={person} ref={(child) => {this.child = child}} />
        <p>count: { count }</p>
      </div>
    )
  }
}

export default App;

Memo

React.MenoReact.pureComponent差不多,区别是:

  • PureComponent要依靠class才能使用,而React.Memo可以和函数式组件一起使用;
  • React.Memo可以和函数式组件一起使用,所以只比较props
  • React.Memo可以传递第二个参数,它可以改变控制对比过程;
  • 与 class 组件中shouldComponentUpdate()方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。

Children组件改为函数式组件,并使用Memo:

const Children = React.memo((props) => {
  console.log('Children Update');
  return (
    <div>Children Component</div>
  )
})

参考链接:

[shouldComponentUpdate不能直接比较object](https://www.cnblogs.com/xufeimei/p/10148643.html)

shouldComponentUpdate 的作用


如果有误,请告诉我,谢谢~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值