博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码看React.PureComponent
阅读量:5944 次
发布时间:2019-06-19

本文共 9781 字,大约阅读时间需要 32 分钟。

本文源码是2018年9月12日拉取的React仓库master分支上的代码

React.PureComponent 官方文档:

Component 与 PureComponent 的区别

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 (                    );    }}复制代码

使用React.PureComponent

class CounterButton extends React.PureComponent {    state = {        count: 1    }    render() {        return (                    );    }}复制代码

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

源码

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

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

本文重点分析 reconciliation阶段 beginWork函数中的 updateClassComponent函数的调用(这一部分在 中重点分析了)

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源码也在不断更新,所以这块逻辑比讲的逻辑多了一个复用逻辑)

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() 来更新复杂数据结构,或者考虑结合 使用,或者直接使用 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,后面的流程与正常更新流程一直,可以参考

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

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

getStateFromUpdate函数源码如下:

function getStateFromUpdate
( workInProgress: Fiber, queue: UpdateQueue
, update: Update
, 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,都会强制更新,所以应该谨慎使用

写在最后

以上就是我对React PureComponent的源码的分享,希望能对有需要的小伙伴有帮助~~~

喜欢我的文章小伙伴可以去 点star ⭐️

转载地址:http://kzzxx.baihongyu.com/

你可能感兴趣的文章
【随想】_无关技术_你是合格的项目经理人吗?
查看>>
Codeforces round 1083
查看>>
Spring Boot Maven插件
查看>>
【转载】Deep learning:十九(RBM简单理解)
查看>>
SSL握手步骤【收藏】
查看>>
day29(对象转xml(使用java))
查看>>
[java]OutOfMemoryError 原因及解决办法
查看>>
Oracle调优总结
查看>>
Springboot - -web应用开发-Servlets, Filters, listeners
查看>>
正态分布
查看>>
[TFRecord文件格式]基本介绍
查看>>
SSM项目整合Quartz
查看>>
rabbitmq安装集群
查看>>
ZOJ2158,POJ1789
查看>>
[转]C#多线程学习(四) 多线程的自动管理(线程池)
查看>>
shell 脚本
查看>>
世界在音乐中得到了完整的再现和表达。
查看>>
数据分析推荐书籍
查看>>
平衡的括号
查看>>
iphone 使用popViewController如何避免内存泄露
查看>>