React 组件的性能优化

124 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

React 组件的性能优化

在 React 中如何对组件的性能进行优化,主要有以下几点:

  • 减轻 state 存储
  • 避免不必要的重新渲染
  • 使用纯组件

1. 减轻 state 存储

如何减轻 state 存储来优化组件的性能呢?其实只需要在存储时只存储跟组件渲染相关的数据即可,比如页面需要展示的列表数据,loading 效果等等;而那些不用做渲染的数据就不要放在 state 中了,比如定时器等等;而对于这种需要在多个方法中用到的数据,可以放在 this 中。

如以下代码中 num 就是需要在页面上渲染的数据,可放在** state** 中;而定时器 timer 不需要在页面中渲染,但是又需要在 componentDidMount 和 componentWillUnmount 中用到,所以放在 **this **中:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0
    };
  }

  handleClick = () => {
    this.setState(
      state => ({ num: state.num + 1 })
    )
  }

  render() {
    return (
      <div className='app' >
        {this.state.num > 5 ? <p>最大数值只能是5!</p> : <Child num={this.state.num} />}
        <button onClick={this.handleClick}>num + 1</button>
      </div>
    );
  }
}

// 子组件
class Child extends React.Component {
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('定时器正在执行~');
    }, 2000)
  }

  componentWillUnmount() {
    console.log("子组件即将卸载~");
    clearInterval(this.timer);
    console.log("清除定时器~");
  }

  render() {
    return (
      <p>次数统计:{this.props.num}</p>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

2. 避免不必要的重新渲染

之前我有说到 React 组件的更新机制就是如果某个组件重新渲染,那么其下面的子组件树也会跟着重新渲染,这也就说明即使子组件中没有任何变化,那么也会渲染,那这种就是属于不必要的渲染,也会影响组件的性能。对于这种情况可以使用钩子函数 shouldComponentUpdate 来解决,通过该钩子函数的返回值(true 或 false)来决定是否重新渲染组件。

代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0
    };
  }

  // 钩子函数
  shouldComponentUpdate(nextProps, nextState) {
    // 获取最新的状态
    console.log('父组件App最新的状态:', nextState)
    // 获取更新前的状态
    console.log('父组件App更新前的状态:', this.state)

    // 该返回值决定是否渲染该组件(true:重新渲染,false:不重新渲染)
    if (nextState.num !== this.state.num) {
      return true
    } else {
      return false
    }
  }

  handleClick = () => {
    this.setState(() => ({
      num: Math.floor(Math.random() * 3)
    })
    )
  }

  render() {
    console.log('父组件App更新了!')
    return (
      <div className='app' >
        <p>父组件次数统计:{this.state.num}</p>
        <Child num={this.state.num} />
        <button onClick={this.handleClick}>num + 1</button>
      </div>
    );
  }
}

// 子组件
class Child extends React.Component {
  // 钩子函数
  shouldComponentUpdate(nextProps, nextState) {
    // 获取最新的状态
    console.log('子组件Child最新的props:', nextProps)
    // 获取更新前的状态
    console.log('子组件Child更新前的props:', this.props)

    // 该返回值决定是否渲染该组件(true:重新渲染,false:不重新渲染)
    if (nextProps.num !== this.props.num) {
      return true
    } else {
      return false
    }
  }

  render() {
    console.log('子组件Child更新了!')
    return (
      <p>子组件次数统计:{this.props.num}</p>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

演示如下:

image.png

  • 当父组件状态发生改变时,父组件的 shouldComponentUpdate 钩子函数将返回 true,页面将重新渲染;
  • 而由于子组件接收的是父组件的状态,所以子组件的 shouldComponentUpdate 钩子函数返回的也是 true,页面也重新渲染了;
  • 如果父组件状态不变,则父组件的 shouldComponentUpdate 钩子函数将返回 false,页面不会重新渲染,那么子组件也不会重新渲染。

3. 使用纯组件来进行性能优化

纯组件就是 React.PureComponent,其功能与 React.Component 功能相似,其优点是自动实现 shouldComponentUpdate 钩子函数,纯组件内部可以通过分别对比 props 和 state 的值来决定是否重新渲染组件,不需要再手动比较了。

代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      num: 0
    };
  }

  handleClick = () => {
    this.setState(() => ({
      num: Math.floor(Math.random() * 3)
    })
    )
  }

  render() {
    console.log('父组件App更新了!')
    return (
      <div className='app' >
        <p>父组件中的随机数为:{this.state.num}</p>
        <Child num={this.state.num} />
        <button onClick={this.handleClick}>生成随机数</button>
      </div>
    );
  }
}

// 子组件
class Child extends React.PureComponent {
  render() {
    console.log('子组件Child更新了!')
    return (
      <p>子组件中的随机数为:{this.props.num}</p>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

演示如下:

image.png

与方法2中效果一致,而且不用写 shouldComponentUpdate 钩子函数,代码更简洁。

但是使用纯组件时会出现一个问题,那就是由于纯组件的内部对比是一个浅层对比,所以如果只是比较值类型的值,那么没有问题,而如果比较的是引用类型的值,那么就会有问题,因为引用类型比较的是对象的地址是否相同。

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      people: {
        age: 20
      }
    };
  }

  handleClick = () => {
    const newPeople = this.state.people
    newPeople.age = Math.floor(Math.random() * 30)

    this.setState(() => ({
      people: newPeople
    })
    )
  }

  render() {
    console.log('父组件App更新了!')
    return (
      <div className='app' >
        <p>年龄:{this.state.people.age}</p>
        <button onClick={this.handleClick}>生成随机的年龄</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

解决这个问题的办法就是我们在使用纯组件对比引用类型的值时,应该创建新的数据,不要直接修改原数据。 

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      people: {
        age: 20
      }
    };
  }

  handleClick = () => {
    // 创建新的对象
    const newPeople = { ...this.state.people, age: Math.floor(Math.random() * 30) }
    // 或深拷贝
    // const newPeople = JSON.parse(JSON.stringify(this.state.people))
    // newPeople.age = Math.floor(Math.random() * 30)

    this.setState(() => ({
      people: newPeople
    })
    )
  }

  render() {
    console.log('父组件App更新了!')
    return (
      <div className='app' >
        <p>年龄:{this.state.people.age}</p>
        <button onClick={this.handleClick}>生成随机的年龄</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

对于数组,不要用 push/unshift 等直接修改当前数组的方法,而应该用 concat/slice等返回新数组的方法,代码如下:

// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'

// App组件
class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      numList: [0, 1, 2, 3, 4, 5]
    };
  }

  handleClick = () => {
    const newNumList = this.state.numList.concat([6, 7, 8, 9, 10])
    this.setState(() => ({
      numList: newNumList
    })
    )
  }

  render() {
    console.log('父组件App更新了!')
    return (
      <div className='app' >
        <p>当前数组:{this.state.numList}</p>
        <button onClick={this.handleClick}>改变数组</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'))