Skip to content
On this page

页面更新优化

类组件 -shouldComponentUpdate

React 给我们提供了一个生命周期方法 shouldComponentUpdate(很多时候,我们简称为 SCU),这个方法接受参数,并且 需要有返回值:

该方法有两个参数:

参数一:nextProps 修改之后,最新的 props 属性

参数二:nextState 修改之后,最新的 state 属性

该方法返回值是一个 boolean 类型

  • 返回值为 true,那么就需要调用 render 方法;
  • 返回值为 false,那么久不需要调用 render 方法;
  • 默认返回的是 true,也就是只要 state 发生改变,就会调用 render 方法;

比如我们在 App 中增加一个 message 属性:

  • jsx 中并没有依赖这个 message,那么它的改变不应该引起重新渲染;
  • 但是因为 render 监听到 state 的改变,就会重新 render,所以最后 render 方法还是被重新调用了;

例子

jsx
import React, {Component} from 'react';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
      message: "Hello World"
    }
  }

  render() {
    // 假设这里是一个计数器
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  shouldComponentUpdate(nextProps, nextState) {
    // 如果值不同就更新、值相同就不更新
    if (this.state.counter !== nextState.counter) {
      return true;
    }

    return false;
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeText() {
    this.setState({
      message: "你好啊,wjw"
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

PureComponent

如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。

思考:shouldComponentUpdate 中的各种判断的目的是什么? 答案:props 或者 state 中的数据是否发生了改变,来决定 shouldComponentUpdate 返回 true 或者 false;

使用

jsx
import React, { PureComponent } from 'react';
// Main
class Banner extends PureComponent {
  render() {
    console.log("Banner render函数被调用");
    return <h3>我是Banner组件</h3>
  }
}

class Main extends PureComponent {
  render() {
    console.log("Main render函数被调用");
    return (
      <div>
        <Banner/>
      </div>
    )
  }
}

export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <Main/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

源码

javascript
// react/blob/main/packages/react/src/ReactBaseClasses.js
/**
 * 方便的组件,默认浅相等检查sCU。
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // 如果一个组件有字符串引用,我们将在以后分配一个不同的对象。
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// 避免这些方法的额外原型跳转
assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

渲染处理

javascript
// react/packages/react-reconciler/src/ReactFiberClassComponent.new.js
// 通过判断
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }
1
2
3
4
5
6
7

浅对比

javascript
// react/packages/shared/shallowEqual.js
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // 测试A和B不同的key值
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

函数化组件 -memo

React.memo() 可接受 2 个参数,第一个参数为纯函数的组件,第二个参数用于对比 props 控制是否刷新,与 shouldComponentUpdate() 功能类似

jsx
import React from "react";

function Child({seconds}){
    console.log('I am rendering');
    return (
        <div>I am update every {seconds} seconds</div>
    )
};

function areEqual(prevProps, nextProps) {
    if(prevProps.seconds===nextProps.seconds){
        return true
    }else {
        return false
    }

}
export default React.memo(Child,areEqual)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

源码

javascript
// react/packages/react/src/ReactMemo.js
export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  if (__DEV__) {
    if (!isValidElementType(type)) {
      console.error(
        'memo: The first argument must be a component. Instead ' +
          'received: %s',
        type === null ? 'null' : typeof type,
      );
    }
  }
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
  if (__DEV__) {
    let ownName;
    Object.defineProperty(elementType, 'displayName', {
      enumerable: false,
      configurable: true,
      get: function() {
        return ownName;
      },
      set: function(name) {
        ownName = name;

        // 在大多数情况下,内部组件不应该继承这个显示名称,
        // 因为该组件可能在其他地方使用。
        // 但是匿名函数继承这个名字很好,
        // 这样我们的组件堆栈生成逻辑将显示它们的框架。
        // 匿名函数通常建议采用如下模式:
        //   React.memo((props) => {...});
        // 这种内部功能没有在其他地方使用,所以副作用是可以接受的。
        if (!type.name && !type.displayName) {
          type.displayName = name;
        }
      },
    });
  }
  return elementType;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
沪ICP备20006251号-1