likes
comments
collection
share

React类组件之Component与PureComponent原理解析

作者站长头像
站长
· 阅读数 12

1,Component

我们在刚开始学习React时,第一个接触的就是class类组件的学习,通过继承React.Component创建类组件:

import React, { Component } from 'react';

export default class MyClass extends Component {
  render() {
    return (
      <div>
        <div>我是class组件</div>
      </div>
    );
  }
}

下面我们就进入react源码【v18.2】,学习Component组件的实现原理:

// react/src/ReactBaseClasses.js

# 类组件的定义
function Component(props, context, updater) {
  // 定义了四个实例属性
  this.props = props;
  this.context = context; // 上下文
  this.refs = emptyObject; // ref引用
  this.updater = updater; // 更新器对象
}

可以看见Component组件的定义非常简单,就是一个普通的构造函数。

在JavaScript中,类(class)是ECMAScript 中的基础性语法糖结构,它的实现原理仍然是构造函数和原型的概念。

定义了几个实例属性,传递的参数有我们最熟悉的propscontext

然后再看Component构造函数在原型对象上定义的属性和方法:

# 原型上定义的属性和方法
// 这个属性用于区分类组件和函数组件
Component.prototype.isReactComponent = {};
// 我们最常用的方法,设置state,触发应用的重新渲染
Component.prototype.setState = function(partialState, callback) {}
// 强制组件重新渲染
Component.prototype.forceUpdate = function(callback) {}

下面我们主要看setStateforceUpdate两个方法的实现原理:

setState
# setState原理
Component.prototype.setState = function(partialState, callback) {
  // 调用updater中的一个方法
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

setState方法自身的内容非常简单,仅仅是调用了实例属性updater更新器对象中的一个方法,要继续向下解析,首先我们得知道updater从何而来。

根据react源码我们可以知道,在react应用加载时,当类组件创建Fiber节点后,会创建与之对应的组件实例Instance

关于react中类组件的具体加载逻辑可以查看《React18.2x源码解析:类组件的加载过程》

// packages\react-reconciler\src\ReactFiberBeginWork.new.js

function updateClassComponent() {
    ...
    const instance = workInProgress.stateNode;
    // 第一次class组件加载,instance都为null
    if (instance === null) {
        // 构建组件实例
        constructClassInstance(workInProgress, Component, nextProps);
        // 加载组件实例
        mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    }
}

调用constructClassInstance方法构建组件实例,当instance实例创建完成之后,会调用一个方法adoptClassInstance

function constructClassInstance() {
    const instance = ...
    ...
    adoptClassInstance(workInProgress, instance);
}
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  # 设置实例对象的updater属性
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // 链接FiberNode与instance
  setInstance(instance, workInProgress);
}

这个方法有两个作用:

  • 设置class组件实例的updater属性。
  • 链接class组件对应的FiberNodeInstance

这里我们主要关注第一个点即可。

下面我们继续查看classComponentUpdater对象的定义:

// packages\react-reconciler\src\ReactFiberBeginWork.new.js

const classComponentUpdater = {
  isMounted,
  # 这就是setState调用的方法
  enqueueSetState(inst, payload, callback) {
    // 取出instance实例对应的FiberNode节点
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    // 获取FIber对应的lane优先级
    const lane = requestUpdateLane(fiber);
    // 创建update更新对象
    const update = createUpdate(eventTime, lane);
    // 确定更新内容 { count: 1 }
    update.payload = payload; 
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      // 存储传入的cb回调函数
      update.callback = callback;
    }
    // 将update更新对象添加到更新队列,并返回应用的root根节点
    const root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      // 开启一个从root根节点开始的更新调度
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitions(root, fiber, lane);
    }
  },
      
  enqueueReplaceState() {},
  enqueueForceUpdate() {}
}

重点: 当我们在class组件中调用this.setState()时,等同于在调用:

// this.setState()方法的本质
classComponentUpdater.enqueueSetState()

enqueueSetState方法里面的内容并不多,并且在这里我们只需要关注最后一行代码:

scheduleUpdateOnFiber(root, fiber, lane, eventTime);

scheduleUpdateOnFiber这个方法在react源码中非常重要,它的作用就是开启一个新的调度更新任务【应用更新】。

关于react应用是如何开启一个调度更新任务的可以查看《React18.2x源码解析(一)react应用加载》

所以setState()方法的原理就是: 将更新的相关的信息存储到Class组件对应的Fiber节点上,然后开启一个新的调度更新任务,触发一次react应用的重新渲染流程。

forceUpdate
# 强制组件更新
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

forceUpdate方法原理和setState方法非常相似,同样是调用的updater更新器对象中的方法:

const classComponentUpdater = {
  
  enqueueForceUpdate(inst, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(eventTime, lane);
    # 设置了update更新对象的tag标识为强制更新
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'forceUpdate');
      }
      update.callback = callback;
    }

    const root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitions(root, fiber, lane);
    }
  },
}

可以看到两个方法的逻辑基本一样,唯一的区别就是enqueueForceUpdate方法中给update更新对象设置的tag标识为ForceUpdate,代表着该组件本次为强制更新,这意味着该组件本次更新会跳过 shouldComponentUpdate()方法。

每个类组件在更新阶段时,都会进行是否需要更新的条件判断:

# 更新class组件实例
function updateClassInstance(
    // 决定本组件是否应该更新
    const shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate()
    return shouldUpdate;
)

注意: 这里决定类组件是否应该更新,主要就是就是根据两个方法的返回值来确定的,只要有任何一个结果返回ture,则代表本组件应该更新。

  • checkHasForceUpdateAfterProcessing方法的返回值就是根据当前组件是否设置了ForceUpdate强制更新标记来决定的。
  • checkShouldComponentUpdate方法的返回值则是调用shouldComponentUpdate钩子函数结果来决定的。

所以只要checkHasForceUpdateAfterProcessing()方法调用后返回了true,则不会再调用后面的checkShouldComponentUpdate方法。

所以forceUpdate()方法的原理就是: 设置本次更新该类组件为强制更新,在组件更新判断时默认返回true,会跳过该组件的 shouldComponentUpdate()方法。

2,PureComponent

我们接着再看PureComponent纯组件,其实纯组件的实现原理和Component基本一致:

// react/src/ReactBaseClasses.js

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

他们的构造函数定义完全一样,并且继承了Component的原型对象:

// Component仿制品
function ComponentDummy() {}
// 存储了Component的原型对象
ComponentDummy.prototype = Component.prototype;
// 原型继承
PureComponent.prototype = new ComponentDummy()

但是PureComponent纯组件在自己的原型对象上设置了一个新的静态属性,用于区分普通类组件。

// 区分普通的类组件
PureComponent.prototype.isPureReactComponent = true;

总结: 所以PureComponentComponent的定义可以说完全一样,唯一的区别就是PureComponent多了一个标记属性。

这个属性的作用就是在类组件更新时,react内部自动帮助我们对stateprops进行浅比较,来决定组件是否更新。

function updateClassInstance(
    // 决定本组件是否应该更新
    const shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate()
    return shouldUpdate;
)
# 检测组件是否应该更新
function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  // 取出组件实例
  const instance = workInProgress.stateNode;
  // 如果用户定义了shouldComponentUpdate钩子
  if (typeof instance.shouldComponentUpdate === 'function') {
    // 调用shouldComponentUpdate钩子
    let shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    )
    // 返回调用结果,决定组件是否更新
    return shouldUpdate;
  }
  
  # 如果是纯组件,则自动帮助我们进行浅比较
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    // 返回浅比较结果,决定组件是否更新
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }
  return true;
}

checkShouldComponentUpdate方法中,这里根据是否存在isPureReactComponent属性来确定是否为纯组件,如果为是,则react内部会自动帮助我们对对stateprops进行浅比较,来决定组件是否更新,这就是PureComponent纯组件的核心原理。

结束语

以上就是react中类组件和纯组件实现原理的全部内容了,觉得有用的可以点赞收藏!如果有问题或建议,欢迎留言讨论!