从源码看React.PureComponent

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

内容简介: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,都会强制更新,所以应该谨慎使用


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

查看所有标签

猜你喜欢:

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

嗨翻C语言

嗨翻C语言

[美]David Griffiths、[美]Dawn Griffiths / 程亦超 / 人民邮电出版社 / 2013-9 / 99.00

你能从这本书中学到什么? 你有没有想过可以轻松学习C语言?《嗨翻C语言》将会带给你一次这样的全新学习 体验。本书贯以有趣的故事情节、生动形象的图片,以及不拘一格、丰富多样的练 习和测试,时刻激励、吸引、启发你在解决问题的同时获取新的知识。你将在快乐 的气氛中学习语言基础、指针和指针运算、动态存储器管理等核心主题,以及多线 程和网络编程这些高级主题。在掌握语言的基本知识......一起来看看 《嗨翻C语言》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具