React.PureComponent原理篇

224 阅读3分钟

React类组件性能优化

类组件继承React.Component类会有一个shouldComponentUpdate的生命周期,在这个生命周期中可以决定组件是否re-render,默认值为truetrue为更新,false为不更新,shouldComponentUpdate会接收两个参数nextPropsnextState,通过判断两个参数是否有更改决定组件是否更新

  • nextProps: 表示下一个props。
  • nextState: 表示下一个state的值。
import React, { Component } from "react"

export class App extends Component {

  // 简单举个例子,实际中肯定要更加深层比较
  shouldComponentUpdate(nextProps, nextState){
    if(nextProps === this.props) return false
    if(nextState === this.state) return false
    return true
  }

  render() {
    return (
      <div>App</div>
    )
  }
}

export default App

通过比较propsstate的变化去觉得是否需要更新组件,但是每一个页面都需要去判断,那么对于我们开发来说会非常的繁琐,所以React给我们封装了一个React.PureComponent的类,我们的类组件通过继承React.PureComponent则不需要我们手动判断组件是否更新,内部会帮助我们判断数据是否变化

PureComponent的使用方法

当我们继承了PureComponent,我们就不需要担心执行没有意义得到render,下面通过一个简单的例子:页面中有一个text属性,点击按钮把text'Hello World!'改为'Hello World!',观察render函数是否有执行

import React, { PureComponent } from "react"

export class App extends PureComponent {
  constructor() {
    super()
    this.state = {
      text: "Hello World!",
    }
  }

  change() {
    this.setState({
      text: "Hello World!",
    })
  }

  render() {
    console.log("render")
    const { text } = this.state
    return (
      <>
        <div>{text}</div>
        <button onClick={() => this.change()}>change</button>
      </>
    )
  }
}

export default App

PureComponent源码解析

ReactBaseClasses.js文件中,给PureComponent组件添加了一个标识

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); 
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype); 
pureComponentPrototype.isPureReactComponent = true;

搜索isPureReactComponent属性可查看其作用是用于比较props和state

// 如果是PureComponent组件则判断数据是否发生变化
else if (type.prototype && type.prototype.isPureReactComponent) {
  shouldUpdate = !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}

shallowEqual是对比两个属性是否一样,注意:这里做的是浅层比较,深层属性发生变化时是不会更新的

import is from './objectIs';
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 *
 * 通过遍历对象上的键来执行相等性,
 * 当任意键的值在参数之间不严格相等时返回false,
 * 当所有键的值严格相等时,返回true。
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  // is 方法同等 Object.js 这里为了兼容性而封装的 is 方法
  // 当两个值相等则返回 true 引用类型则比较地址
  if (is(objA, objB)) {
    return true;
  }
  // objA 或者 objB 不为对象时直接返回 false
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }
  // 拿到 objA 和 objB 的所有 key
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  // 两个对象的 key 数量不一致则返回 false
  if (keysA.length !== keysB.length) {
    return false;
  }
  // 当 objA 和 objB 都有共同的 key 值则比较值是否相同
  // 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函数返回false则更新,返回true则不更新,因为外部调用时进行了取反操作

继承PureComponent需注意

我们通过查看shallowEqual源码发现,当前后的对象引用地址相同时,组件是不会更新的,这个时候就可能会出现不可预计的错误了,React官方是推出了一个名词不可变数据,意思就是不要直接修改state中的数据,下面举个例子

import React, { PureComponent } from "react"

export class App extends PureComponent {
  constructor() {
    super()
    this.state = {
      arr: [1, 2, 3],
    }
  }

  add() {
    this.state.arr.push(4)
    this.setState({
      arr: this.state.arr,
    })
  }

  render() {
    return (
      <>
        <ul>
          {this.state.arr.map((item) => (
            <li key={item}>{item}</li>
          ))}
        </ul>
        <button onClick={() => this.add()}>+</button>
      </>
    )
  }
}

export default App

当我们点击添加时,视图是不会发生变化的,add函数通过push修改了原数组,调用setStatethis.state.arr传个arr,但是他们的引用地址是相同的,所以React内部以为你没有改变,所以不执行render渲染视图,这样修改属性值也是不建议的

add() {
  const newArr = [...this.state.arr]
  newArr.push(4)
  this.setState({
    arr: newArr
  })
}

通过一个浅拷贝创建一个新的对象再进行操作,这样就可以触发视图更新了