React Hooks - useEffect、useLayoutEffect

138 阅读1分钟

1、useEffect 源码分析

mount 阶段

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 生成 hook 对象
  const hook = mountWorkInProgressHook();
  
  // 获取更新的依赖
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  
  // 保存最新生成的 effct 对象(多个effect对象、单向环状链表)
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}


// 创建 effect 对象的函数
function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    next: (null: any),
  };
  // 保存 update 对象的 componentUpdateQueue 队列
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  // 返回 effect 对象
  return effect;
}


// 创建 hook 对象的函数
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

update 阶段

function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 生成 hook 对象(此函数参考 useState 那一节)
  const hook = updateWorkInProgressHook();
  
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;

  if (currentHook !== null) {
    // 取出上次产生的 effect 的 destory 函数
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 依赖没有变化时
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 保障单项链表位置的一致性
        hook.memoizedState = (hookFlags, create, destroy, nextDeps);
        // 中断
        return;
      }
    }
  }

  // 依赖发生变化时执行以下
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}

2、useLayoutEffect 源码分析

mount 阶段

function mountLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // 最终调用 mountEffectImpl
  return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
}

update 阶段

function updateLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // mountEffectImpl
  return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}

3、执行流程

// 进入 commit 阶段
commitRoot(root)
    |
    |
    V
commitRootImpl(root, renderPriorityLevel)  -->   commitLayoutEffects(root, lanes);
    |                                                        |
    |                                                        |
    V                                                        V
commitBeforeMutationEffects();                   commitLifeCycles()
    |                                                        |
    |                                                        |
    V                                                        V
flushPassiveEffects()                            commitHookEffectListMount
scheduleCallback(                                // Mount
    NormalSchedulerPriority,                     const create = effect.create;
    () => {                                      effect.destroy = create();
        flushPassiveEffects();
        return null;
    }
);