一、前言
最近的几次面试都被问到了框架原理。记忆力不太行的我往往背诵完后很快就会忘记,因此打算花21个小时(分3天,每天7小时)去好好的阅读下React框架,了解其中几个关键功能点的执行逻辑。
框架版本:React 17,enableNewReconciler=false
学习方式:通过debug一步步调试JS代码的方式,来深入了解React框架的主流程
文中的代码片段,只保留作者认为重要的代码
二、DAY 3 目标
- 函数组件的渲染逻辑(hooks功能)
- 调用useState的dispatch后的主要逻辑
三、初始化函数组件流程图
关键点
- 根据是否是第一次运行,判断HooksDispatch是使用onMount还是onUpdate。
- 每次调用hook,都会创建hook对象,并按照顺序形成链表。同时会把第一个hook赋值到currentlyRenderingFiber.memoizedState。
四、调用useState的dispatch
从初始化函数组件调用useState里可以看出,返回的dispatch方法其实是dispatchAction。
dispatchAction主要做2件事情:
- 把需要改变的值更新当前hook的pending里(方便后续re-render时获取最新state)
- 调用scheduleUpdateOnFiber,给syncQueue添加回调后返回执行后续的函数执行(最终通过事务机制触发flushSyncCallbackQueue方法)
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// Append the update to the end of the list.
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
五、重新渲染函数组件流程图
重新渲染的流程一致,只是在执行hooks时调用updateHooks。
通过current Fiber的memoizedState获取对应的hook。
根据第四步更新的pending对象,新建newHook,并更新计算出最新的值。
需要注意的是:调用updateWorkInProgressHook时,新建了newHook,但里面的属性还是指向原Hook属性的引用(同一个对象)。只有在需要更新的情况(比如调用setate后memoizedState的更新)里面的属性才是新的对象。
六、总结
在调和和渲染阶段,函数组件和class组件最大的区别我认为最大的2点不同:
- 没有实例对象,对应Fiber的stateNode是null,每次重新渲染都是函数从头至尾运行一次。
- 调用hooks后生成的hooks链表,会存储在Fiber.memoizedState里,方便取用。