React 如何实例化组件?

2022-12-21 20:07:47 浏览数 (1)

我们写的组件分为 函数组件 和 类组件。

类组件

源码在 ReactFiberClassComponent.new.js 文件下,并在函数 constructClassInstance 中 做实例‍化 。

代码语言:javascript复制
function constructClassInstance(
  workInProgress, // Fiber
  ctor, // 类组件,ctor 是 constructor(构造器)的意思
  props
) {
  let instance = new ctor(props, context);
}

在这里我还发现了一个有趣的地方,就是在开发模式的 StrictMode 下,组件会被实例化两次。第二次实例化还会劫持 console,把要打印的内容丢掉。 实例化两次,主要是像帮助开发者发现一些组件的副作用(side Effer)错误。比如 useEffect 中绑定了事件,却忘记解绑事件。

在构建一个 Fiber 的过程中,如果发现 Fiber.tag 是 ClassComponent (对应的值是 1),就会调用上面这个 constructClassInstance 方法,创建一个实例对象,然后把它挂在 Fiber.stateNode 上。

此外,这个实例也会用一个属性 _reactInternals 关联对应的 Fiber。二者互相引用属于是。

Component 构造函数

类组件需要继承 React.Component,然后在构造函数 constructor 下执行 super(),其实就是调用 React.Component 构造函数。

这个 Component 的源码如下:

代码语言:javascript复制
const emptyObject = {};

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.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
// 强制更新
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

看了上面这段源码,我们知道

  1. 调用 super 时,记得带上 props,否则 props 会丢失。
  2. 更新状态的 setState 和 forceUpdate 其实是调用的是底层的 update 的 enqueueSetState 和 enqueueForceUpdate 方法。

classComponentUpdater

在类组件中,组件实例的 updater 最终指向 classComponentUpdater。

classComponentUpdater 其实只是一堆方法的集合,但会操作传入的参数,往上面加一些东西,更新队列最终是会放到 fiber 对象上。

先看看 classComponentUpdater.enqueueSetState 的核心实现:

代码语言:javascript复制
const classComponentUpdater = {
  // setState 实际调用的方法
  enqueueSetState(
    inst, // 组件实例对象
    payload, // setState 第一个参数,类型是对象或函数
    callback // setState 第二个参数
  ) {
    // 创建一个 update 对象。
  const update = createUpdate(eventTime, lane);
    // setState 的参数挂到 update 上
    update.payload = payload;
    update.callback = callback;
    // update 对象入队到待更新队列 fiber.updateQueue.shared.pending
    // 这是一个链表形式的队列
    enqueueUpdate(fiber, update, lane);
    // 调度更新
    scheduleUpdateOnFiber(root, fiber, lane, eventTime);
  }
}

创建一个 updater 对象,将 setState 的参数放到 update 下。

update 对象长这样:

image-20221121235611915

然后使用 enqueueUpdate 方法将 update 保存到当前 fiber 的 updateQueue 下,具体位置是 fiber.updateQueue.shared.pending,是个链表。

然后是 scheduleUpdateOnFiber 方法,会将所在 fiber 树标记为 “存在准备更新的队列”,然后开始调度更新。

函数组件

代码语言:javascript复制
function renderWithHooks<Props, SecondArg>(
  current: Fiber | null, // 函数组件实例对应 Fiber
  workInProgress: Fiber,
  // 函数组件
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  let children = Component(props, secondArg);
}

这里的 children 是 ReactElement,不是一个实例对象,因为我们没用 new 关键字。

同理,在构建一个 Fiber 时,如果 Fiber.tag 是 FunctionComponent(对应值为 0),就会调用上面这个 renderWithHooks。

但因为函数组件不会创建实例,所以 Fiber.stateNode 还是 null。

结尾

简单说了一下 React 的实例化执行的相关的函数。

我是前端西瓜哥,欢迎关注我,学习更多前端知识。


0 人点赞