我们写的组件分为函数组件和类组件。
类组件
源码在 ReactFiberClassComponent.new.js 文件下,并在函数 constructClassInstance 中 做实例化 。
function constructClassInstance(workInProgress, // Fiberctor, // 类组件,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'); };
看了上面这段源码,我们知道
- 调用 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);}}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 对象长这样:
![React如何实例化组件? 图片[1]-React如何实例化组件?-不念博客](https://www.bunian.cn/wp-content/uploads/2022/11/weixintupian20221128163921.png)
然后使用 enqueueUpdate 方法将 update 保存到当前 fiber 的 updateQueue 下,具体位置是 fiber.updateQueue.shared.pending,是个链表。
然后是 scheduleUpdateOnFiber 方法,会将所在 fiber 树标记为 “存在准备更新的队列”,然后开始调度更新。
函数组件
function renderWithHooks<Props, SecondArg>(current: Fiber | null, // 函数组件实例对应 FiberworkInProgress: 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 的实例化执行的相关的函数。