React如何实例化组件?

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

类组件

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

function constructClassInstance(
workInProgress, // Fiber
ctor, // 类组件,ctor 是 constructor(构造器)的意思
props
) {
let instance = new ctor(props, context);
}
function constructClassInstance(
  workInProgress, // Fiber
  ctor, // 类组件,ctor 是 constructor(构造器)的意思
  props
) {
  let instance = new ctor(props, context);
}
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 的源码如下:

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');
};
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');
};
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 的核心实现:

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);
}
}
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);
  }
}
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 对象长这样:

图片[1]-React如何实例化组件?-不念博客
update对象

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

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

函数组件

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);
}
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);
}
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 的实例化执行的相关的函数。

© 版权声明
THE END