React组件性能优化

101 阅读3分钟

image.png 该文章主要介绍shouldComponentUpdate、pureComponent、memo如何对组件进行优化以及优缺点

1.前提提要

例如在App根组件中我们对其中的state进行数据状态的更改,render函数被调用时,其所有子组件都要重新render并进行diff算法,其性能必然是很低的,执行render的前提是state和props发生改变,然而并不是所有组件都要执行render。如下例子所示

<APP父组件>
import React, { Component } from "react";
import Child from "./Child";
import Child2 from "./Child2";
export class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 1,
    };
  }
  addCount() {
    this.setState({ count: this.state.count+1 });
  }
  render() {
    console.log("APP被render");
    const { count } = this.state;
    return (
      <div>
        <h2>count:{count}</h2>
        <Child></Child>
        <Child2></Child2>
        <button onClick={e => this.addCount()}>count++</button>
      </div>
    );
  }
}

export default App;

<child子组件>
import React, { Component } from "react";
export class child extends Component {
  render() {
    console.log('child被render');
    return <h2>child</h2>;
  }
}
export default child;
import React, { Component } from 'react'
export class child2 extends Component {
  render() {
    console.log('child2被render');
    return (
      <h2>child2</h2>
    )
  }
}
export default child2

在APP父组件中,对其内部的count值进行修改,即使child和child2组件没有依赖count值,也会导致子组件执行render,这大大降低了组件的性能。

image.png

2.shouldComponentUpdate

shouldComponentUpdate简称SCU,该生命周期有两个参数

  1. 参数一:nextProps修改之后,最新的props值
  2. 参数二:nextState修改之后,最新的state值

返回值

  1. 返回值为true,那么就需要调用render方法
  2. 返回值为false,就不需要掉render方法
  3. 默认返回true
export class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 1,
    };
  }
  addCount() {
    this.setState({ count: 1 });
  }
  shouldComponentUpdate(nextProps, nextState) {
    if (nextState.count === this.state.count) {
      return false;
    }
    return true;
  }
  render() {
    console.log("APP被render");
    const { count } = this.state;
    return (
      <div>
        <h2>count:{count}</h2>
        <Child></Child>
        <Child2></Child2>
        <button onClick={e => this.addCount()}>count++</button>
      </div>
    );
  }
}

如上代码所示,在生命周期shouldComponentUpdate对state值进行是否更改来判断是否进行render,addCount函数并没有对state值进行更改所以不会执行render。但问题是如果组件维护了多个状态就要进行多次判断,甚至如果子组件也依赖其某个状态也要进行多次值是否更改的判断,这会导致代码非常冗余。下面将介绍shouldComponentUpdate的升级版pureComponent。

3.pureComponent

大部分情况下可以用pureComponent代替手写的shouldComponentUpdate,但pureComponent只对state和props进行浅比较,对复杂的数据结构,情况会比较麻烦,下节将会介绍解决办法。

<APP父组件>
export class App extends PureComponent {
  constructor() {
    super();
    this.state = {
      count: 1,
      val: 1,
    };
  }
  addCount() {
    this.setState({ count: this.state.count + 1 });
  }
  addVal() {
    this.setState({ val: this.state.val + 1 });
  }
  render() {
    console.log("APP被render");
    const { count, val } = this.state;
    return (
      <div>
        <h2>count:{count}</h2>
        <Child count={count}></Child>
        <Child2 val={val}></Child2>
        <button onClick={e => this.addCount()}>count++</button>
        <button onClick={e => this.addVal()}>addVal++</button>
      </div>
    );
  }
}
<Child子组件>
import React, { PureComponent } from "react";
export class child extends PureComponent {
  constructor(props) {
    super(props);
  }
  render() {
    const { count } = this.props;
    console.log("child被render");
    return <h2>child:{count}</h2>;
  }
}
export default child;
<Child2子组件>
import React, { PureComponent } from "react";

export class child2 extends PureComponent {
  constructor(props) {
    super(props);
  }
  render() {
    const { val } = this.props;
    console.log("child2被render");
    return <h2>child2:{val}</h2>;
  }
}

export default child2;

image.png Child子组件依赖count状态,child2依赖val状态,当只对val状态进行更改时,只有其依赖count的组件才会进行render。上述例子都是class组件,但PureComponent在函数式组件中无法使用,函数式组件使用memo对依赖的props状态进行比较来判断是否render。

import {memo} from 'react'
const Profile = memo(function(props){
    render xxxx
})
export default Profile

4.不可变数据的力量

前节讲到PureComponent只能对浅层的数据比较,但对复杂的数据结构就会有遗漏。如下所示

export class App extends PureComponent {
  constructor() {
    super();
    this.state = {
      roles: [
        { name: "早见沙织", country: "Japan" },
        { name: "陶典", country: "China" },
        { name: "菊花花", country: "China" },
        { name: "花玲", country: "China" },
      ],
    };
  }
  addRole() {
    const newRole = { name: "我妻由乃", country: "Japan" };
    this.state.roles.push(newRole);
    this.setState({ roles: this.state.roles });
  }
  render() {
    console.log("render");
    const { roles } = this.state;
    return (
      <div>
        <ul>
          {roles.map((item, index) => {
            return (
              <li key={index}>
                <span>
                  name:{item.name}--country:{item.country}
                </span>
              </li>
            );
          })}
          <button onClick={e => this.addRole()}>addRole</button>
        </ul>
      </div>
    );
  }
}

在上述代码中,this.setState({ roles: this.state.roles }),直接修改原有state,并不会对数据进行更改,避免该问题最简单的方式是避免直接更改你正用于 props 或 state 的引用类型数据值。

addRole() {
    const newRole = { name: "我妻由乃", country: "Japan" };
    const roles = [...this.state.roles]; // 引用类型的不可变,进行了浅拷贝地址改变
    roles.push(newRole);
    this.setState({ roles: roles });
 }

image.png 通过扩展运算符进行浅拷贝改变内存的地址,但其值依然是一样的。 Image.png

5.总结

以上是个人的粗浅认知,如有问题死不悔改哈哈。