DAY 3(React 17 源码,函数组件的渲染逻辑与hooks流程)

112 阅读2分钟

一、前言

最近的几次面试都被问到了框架原理。记忆力不太行的我往往背诵完后很快就会忘记,因此打算花21个小时(分3天,每天7小时)去好好的阅读下React框架,了解其中几个关键功能点的执行逻辑。
框架版本:React 17,enableNewReconciler=false
学习方式:通过debug一步步调试JS代码的方式,来深入了解React框架的主流程
文中的代码片段,只保留作者认为重要的代码

二、DAY 3 目标

  1. 函数组件的渲染逻辑(hooks功能)
  2. 调用useState的dispatch后的主要逻辑

三、初始化函数组件流程图

关键点

  1. 根据是否是第一次运行,判断HooksDispatch是使用onMount还是onUpdate。
  2. 每次调用hook,都会创建hook对象,并按照顺序形成链表。同时会把第一个hook赋值到currentlyRenderingFiber.memoizedState。

四、调用useState的dispatch

从初始化函数组件调用useState里可以看出,返回的dispatch方法其实是dispatchAction。

dispatchAction主要做2件事情:

  1. 把需要改变的值更新当前hook的pending里(方便后续re-render时获取最新state)
  2. 调用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点不同:

  1. 没有实例对象,对应Fiber的stateNode是null,每次重新渲染都是函数从头至尾运行一次。
  2. 调用hooks后生成的hooks链表,会存储在Fiber.memoizedState里,方便取用。