我们写的组件分为函数组件和类组件。
类组件
源码在 ReactFiberClassComponent.new.js 文件下,并在函数 constructClassInstance 中 做实例化 。
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');
};
看了上面这段源码,我们知道
- 调用 super 时,记得带上 props,否则 props 会丢失。
- 更新状态的 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);
}
}
创建一个 updater 对象,将 setState 的参数放到 update 下。
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);
}
这里的 children 是 ReactElement,不是一个实例对象,因为我们没用 new 关键字。
同理,在构建一个 Fiber 时,如果 Fiber.tag 是 FunctionComponent(对应值为 0),就会调用上面这个 renderWithHooks。
但因为函数组件不会创建实例,所以 Fiber.stateNode 还是 null。
结尾
简单说了一下 React 的实例化执行的相关的函数。