从源码看React.PureComponent

栏目: 服务器 · 发布时间: 7年前

内容简介:React.PureComponent 和 React.Component 几乎相同,区别在于 React.PureComponent 会使用 React.PureComponent 也是React应用优化的一种方式,当然也能使用 React.Component 定义看一个简单的例子:
React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

React.PureComponent 和 React.Component 几乎相同,区别在于 React.PureComponent 会 浅比较 props、state是否发生变化从而决定是否更新组件(这里的浅比较在后面的源码分析中会提到)

使用 React.PureComponent 也是React应用优化的一种方式,当然也能使用 React.Component 定义 shouldComponentUpdate 生命周期函数来实现一样的功能,但是直接使用 React.PureComponent 能更加直观和简便

看一个简单的例子:

使用React.Component

class CounterButton extends React.Component {
    state = {
        count: 1
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
            return true;
        }
        if (this.state.count !== nextState.count) {
            return true;
        }
        return false;
    }
    render() {
        return (
            <button
                color={this.props.color}
                onClick={() => this.setState(state => ({count: state.count + 1}))}>
                Count: {this.state.count}
            </button>
        );
    }
}
复制代码

使用React.PureComponent

class CounterButton extends React.PureComponent {
    state = {
        count: 1
    }
    render() {
        return (
            <button
                color={this.props.color}
                onClick={() => this.setState(state => ({count: state.count + 1}))}>
                Count: {this.state.count}
            </button>
        );
    }
}
复制代码

上面两段代码都能避免不必要的组件更新,优化性能

源码

Component & PureComponent 定义

ReactBaseClasses.js

const emptyObject = {};
/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

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;

export {Component, PureComponent};
复制代码

从源码来看,Component 和 PureComponent 基本一样,唯一区别在于 PureComponent 定义了 isPureReactComponenttrue ,这是为了方便在React应用运行过程中区分 Component 和 PureComponent

在分析后续的源码之前,建议小伙伴去看下我的文章: React16源码之React Fiber架构 ,这篇文章分析了React应用整体的执行流程

本文重点分析 reconciliation阶段 beginWork 函数中的 updateClassComponent 函数的调用(这一部分在 React16源码之React Fiber架构 中重点分析了)

beginWork 函数主要有两部分工作:

1、对Context进行处理

2、根据Fiber节点的tag类型,调用对应的update方法

而tag类型为 ClassComponent 的Fiber节点会调用 updateClassComponent 函数,我们来看看 updateClassComponent 函数的核心源码

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  ...
  let shouldUpdate;
  if (current === null) {
    if (workInProgress.stateNode === null) {
      // In the initial pass we might need to construct the instance.
      constructClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
      mountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
      shouldUpdate = true;
    } else {
      // In a resume, we'll already have an instance we can reuse.
      shouldUpdate = resumeMountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
    }
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}
复制代码

执行流程如下:

current为null,表示当前组件第一次渲染

判断当前组件是否需要初始化

  • workInProgress.stateNode === null 表示需要初始化,调用 constructClassInstancemountClassInstance 两个函数
  • 否则,表示组件已初始化,则调用 resumeMountClassInstance 函数复用初始化过的实例

(React源码也在不断更新,所以这块逻辑比 React16源码之React Fiber架构 讲的逻辑多了一个复用逻辑)

current不为null,调用 updateClassInstance

constructClassInstancemountClassInstance 做的工作:

  • constructClassInstance 主要是初始化组件实例,即调用 constructor 构造函数,并注入 classComponentUpdater
  • mountClassInstance 则是调用 getDerivedStateFromProps 生命周期函数(v16)及 UNSAFE_componentWillMount 生命周期函数

从上面的源码可以看到, resumeMountClassInstance 函数和 updateClassInstance 函数都会将返回值赋值给 shouldUpdate 变量,而 shouldUpdate 变量是布尔类型,在后面的流程中,决定是否执行 render 函数

这里以 updateClassInstance 函数为例来看看源码

function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  // 如果新老props不一致,则会调用 UNSAFE_componentWillReceiveProps 生命周期函数
  ...
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    newState = workInProgress.memoizedState;
  }
  // 执行 getDerivedStateFromProps 生命周期函数
  ...
  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextLegacyContext,
    );

  if (shouldUpdate) {
    ...
  } else {
    ...
  }
  ...
  return shouldUpdate;
}
复制代码

重点关注 checkShouldComponentUpdate 函数

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextLegacyContext,
) {
  const instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextLegacyContext,
    );
    stopPhaseTimer();

    return shouldUpdate;
  }

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}
复制代码

执行流程如下:

1、是否有 shouldComponentUpdate 生命周期函数,有则调用此生命周期函数并返回结果( shouldUpdate )

2、判断此组件是否为 PureComponent ,是则执行 shallowEqual 对新老props、新老state进行浅比较,并返回比较结果

3、默认返回true

shallowEqual 函数:

const hasOwnProperty = Object.prototype.hasOwnProperty;
function is(x, y) {
  // SameValue algorithm
  if (x === y) {
    // Steps 1-5, 7-10
    // Steps 6.b-6.e: +0 != -0
    // Added the nonzero y check to make Flow happy, but it is redundant
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // Step 6.a: NaN == NaN
    return x !== x && y !== y;
  }
}
/**
 * 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.
 */
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;
  }
  // 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;
}

export default shallowEqual;
复制代码

可以看到, shallowEqual 真的就是浅比较,所以对于props、state是复杂数据结构如果使用 PureComponent 往往会导致更新问题

当props、state是简单数据结构的组件适合使用 PureComponent,或者使用 forceUpdate() 来更新复杂数据结构,或者考虑结合immutable objects 使用,或者直接使用 Component,自定义 shouldComponentUpdate 生命周期函数

说到 forceUpdate() 可以顺便看下源码,首先看看 forceUpdate 函数定义,在前面也说过在给组件初始化时,会给组件实例注入 classComponentUpdater ,而调用 forceUpdate 其实就是调用 classComponentUpdater.enqueueForceUpdate ,来看看定义

const classComponentUpdater = {
  ...
  enqueueForceUpdate(inst, callback) {
    ...
    const update = createUpdate(expirationTime);
    // !!!
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};
复制代码

可以看到,在将update放入队列之前,执行了 update.tag = ForceUpdate; ,这个标记将在后面用于标识更新是否为 ForceUpdate ,后面的流程与正常更新流程一直,可以参考 React16源码之React Fiber架构

我们再回到 updateClassInstance 函数,在执行 checkShouldComponentUpdate 函数之前,执行了 processUpdateQueue 函数及进行了 checkHasForceUpdateAfterProcessing 函数判断

processUpdateQueue 函数主要是遍历 updateQueue ,调用 getStateFromUpdate 函数

getStateFromUpdate 函数源码如下:

function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  switch (update.tag) {
    case ReplaceState: {
      ...
    }
    case CaptureUpdate: {
      ...
    }
    // Intentional fallthrough
    case UpdateState: {
      ...
    }
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}
复制代码

我们可以看到,此函数是判断update的tag类型,对于 ForceUpdate 类型会将 hasForceUpdate 变量设置为true

checkHasForceUpdateAfterProcessing 函数则是返回 hasForceUpdate 变量,代码如下:

export function checkHasForceUpdateAfterProcessing(): boolean {
  return hasForceUpdate;
}
复制代码

当调用了 forceUpdate 函数,无论是否存在 shouldComponentUpdate 生命周期函数,无论此组件是否为 PureComponent,都会强制更新,所以应该谨慎使用


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Design Patterns

Head First Design Patterns

Elisabeth Freeman、Eric Freeman、Bert Bates、Kathy Sierra、Elisabeth Robson / O'Reilly Media / 2004-11-1 / USD 49.99

You're not alone. At any given moment, somewhere in the world someone struggles with the same software design problems you have. You know you don't want to reinvent the wheel (or worse, a flat tire),......一起来看看 《Head First Design Patterns》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码