React源码之Hooks

680 阅读14分钟

Hooks 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

HooksFunctionComponent的补充,不会替代ClassComponent

没有破坏性改动

在我们继续之前,请记住 Hook 是:

  • 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
  • 100% 向后兼容的。 Hook 不包含任何破坏性改动。
  • 现在可用。 Hook 已发布于 v16.8.0。

没有计划从 React 中移除 class。 你可以在本页底部的章节读到更多关于 Hook 的渐进策略。

Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。

如果不想了解添加 Hook 的具体原因,可以直接跳到下一章节开始学习 Hook! 当然你也可以继续阅读这一章节来了解原因,并且可以学习到如何在不重写应用的情况下使用 Hook。

数据结构

在我们正式进入学习之前,我们需要了解写React有关Hooks的数据结构

  • dispatcher

    在React中有两种dispatcher,一种用于Hooks的创建,一种用于Hooks的更新

// 用于挂载的Dispatcher
const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

// 用于更新的Dispatcher
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

有上面代码可以看出,在mount阶段和update阶段Hooks调用的函数是不同。

FunctionComponent在调用Render之前会根据FunctionComponent对应的Fiber的数据情况来判断调用那个Dispatcher

// 以下代码在`renderWithHooks`中调用
ReactCurrentDispatcher.current = 
  current === null || current.memoizedState === null 
  ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
// render调用
let children = Component(props, secondArg);

那么就一个问题了,Dispatcher是怎么切换的呢?那就要看真是的Hooks的实现了,我们以setState为例:

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}

可以看到,我们每次在调用useState是,useState会从全局获取ReactCurrentDispatcher.current,从而保证我们在mountupdate阶段可以调用不同的Dispatcher

在react中共有四中不同的Dispatcher,在这里我们介绍了两种,感兴趣的同学可以详细的研究每一种的Dispatcher的用法

  • HOOK

HOOK数据结构,就是我们在调用Hooks时创建的数据结构:

export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null,
  next: Hook | null,
};

可以看出HOOK的数据结构和UpdateQueue很像,在HOOK中多了memoizedState,那么这个变量是干什么的?

不同类型hookmemoizedState保存不同类型数据,具体如下:

  • useState: memoizedState保存了state值
  • useReducer: memoizedState保存了state值
  • useEffect: memoizedState保存了Effect数据结构,effect中保存了useEffect的回调函数,依赖项,销毁函数等。
export type Effect = {
  tag: HookFlags,
  create: () => (() => void) | void,
  destroy: (() => void) | void,
  deps: Array<mixed> | null,
  next: Effect,
};
  • useRef: memoizedState保存了{current: null}的对象
  • useMemo: memoizedState保存了[callback(),deps]
  • useCallback: memoizedState保存了[callback,deps],注意,其中useMemo保存的callback执行的结果,而useCallback保存的callback函数本身
  • 有些hook是没有memoizedState的,比如:useContext

极简实现

关于Hooks的极简实现请参考:juejin.cn/post/697087…

下面几让我们进入源码阶段。

入口

所有Hooks的都是useXX,那么让我们来看看这些use函数做什么?


export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

export function useRef<T>(initialValue: T): {|current: T|} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}
// ...省略一些Hooks

有代码可以看出,所有的use函数,都是调用resolveDispatcher获取Dispatcher,调用用相应的use函数。

那么下面我们来看看这些Hooks的实现。

useState和useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

本节我们开始介绍两个Hooks的实现。我们知道,在mount阶段和update阶段,Hooks调用的函数是不相同。我们先从mount阶段看起。

mount

useStatemount阶段,调用的mountState,而useRedmount阶段会调用mountReducer函数:

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
    // 创建hook对象,并将hook挂载到Fiber
    const hook = mountWorkInProgressHook();
    if (typeof initialState === 'function') {
      // $FlowFixMe: Flow doesn't like mixed types
      initialState = initialState();
    }
    // 赋值state
    hook.memoizedState = hook.baseState = initialState;
    // 创建queue
    const queue = (hook.queue = {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer, // 设置默认的basicStateReducer
      lastRenderedState: (initialState: any),
    });
    // 创建dispatch
    const dispatch: Dispatch<
      BasicStateAction<S>,
    > = (queue.dispatch = (dispatchAction.bind(
      null,
      currentlyRenderingFiber,
      queue,
    ): any));
    return [hook.memoizedState, dispatch];
}

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: reducer, // 使用传入的reducer
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

我们对比下两个函数,他们只有细微的差别:

  • 获取initialState方式,有细微不同
  • 创建queue时,setState使用的是默认reducer,而useReducer使用的传入的reducer

basicStateReducer方法如下:

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  // $FlowFixMe: Flow doesn't like mixed types
  return typeof action === 'function' ? action(state) : action;
}

由此可见,setState只是使用basicStateReduceruseReducer

Hooks挂载到Fiber上,这一步是在mountWorkInProgressHook中完成的

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

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

    next: null,
  };

  if (workInProgressHook === null) {
    // 将hooks挂载到Fiber上个
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 形成链表
    workInProgressHook = workInProgressHook.next = hook;
  }
  // workInProgressHook 会在调用完Render后设置为null
  return workInProgressHook;
}

update

update阶段,这两个Hooks分别调用updateStateupdateReducer,其中updateState只是对updateReducer的封装,如下:

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

也就是说,updateState是以basicStateReducer参数的updateReducer。那么下面我们重点关注下updateReducer的实现。

在更新阶段,Hooks链表已经存在在Fiber上了

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
    // 获取hook
    // 获取hook是靠next指针获取的,每次获取一次,next向后移动一个,是个顺序遍历,如果我们将hook放到if条件中,就可能出现错误
    const hook = updateWorkInProgressHook();
    const queue = hook.queue;
    invariant(
      queue !== null,
      'Should have a queue. This is likely a bug in React. Please file an issue.',
    );
    
    // 修改renducer函数
    queue.lastRenderedReducer = reducer;

    const current: Hook = (currentHook: any);

    // The last rebase update that is NOT part of the base state.
    // 渲染树上的baseQueue,可能是因为中断,保存的上一次的updateQueue
    let baseQueue = current.baseQueue;

    // The last pending update that hasn't been processed yet.
    // 将当前的pending挂载到current.baseQueue上
    const pendingQueue = queue.pending;
    if (pendingQueue !== null) {
      // We have new updates that haven't been processed yet.
      // We'll add them to the base queue.
      if (baseQueue !== null) {
        // Merge the pending queue and the base queue.
        const baseFirst = baseQueue.next;
        const pendingFirst = pendingQueue.next;
        baseQueue.next = pendingFirst;
        pendingQueue.next = baseFirst;
      }
      if (__DEV__) {
        if (current.baseQueue !== baseQueue) {
          // Internal invariant that should never happen, but feasibly could in
          // the future if we implement resuming, or some form of that.
          console.error(
            'Internal error: Expected work-in-progress queue to be a clone. ' +
              'This is a bug in React.',
          );
        }
      }
      current.baseQueue = baseQueue = pendingQueue;
      queue.pending = null;
    }

    // 计算出newState
    if (baseQueue !== null) {
      // We have a queue to process.
      const first = baseQueue.next;
      let newState = current.baseState;

      let newBaseState = null;
      let newBaseQueueFirst = null;
      let newBaseQueueLast = null;
      let update = first;
      do {
        const updateLane = update.lane;
        if (!isSubsetOfLanes(renderLanes, updateLane)) {
          // Priority is insufficient. Skip this update. If this is the first
          // skipped update, the previous update/state is the new base
          // update/state.
          const clone: Update<S, A> = {
            lane: updateLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          if (newBaseQueueLast === null) {
            newBaseQueueFirst = newBaseQueueLast = clone;
            newBaseState = newState;
          } else {
            newBaseQueueLast = newBaseQueueLast.next = clone;
          }
          // Update the remaining priority in the queue.
          // TODO: Don't need to accumulate this. Instead, we can remove
          // renderLanes from the original lanes.
          currentlyRenderingFiber.lanes = mergeLanes(
            currentlyRenderingFiber.lanes,
            updateLane,
          );
          markSkippedUpdateLanes(updateLane);
        } else {
          // This update does have sufficient priority.

          if (newBaseQueueLast !== null) {
            const clone: Update<S, A> = {
              // This update is going to be committed so we never want uncommit
              // it. Using NoLane works because 0 is a subset of all bitmasks, so
              // this will never be skipped by the check above.
              lane: NoLane,
              action: update.action,
              eagerReducer: update.eagerReducer,
              eagerState: update.eagerState,
              next: (null: any),
            };
            newBaseQueueLast = newBaseQueueLast.next = clone;
          }

          // Process this update.
          if (update.eagerReducer === reducer) {
            // If this update was processed eagerly, and its reducer matches the
            // current reducer, we can use the eagerly computed state.
            newState = ((update.eagerState: any): S);
          } else {
            const action = update.action;
            newState = reducer(newState, action);
          }
        }
        update = update.next;
      } while (update !== null && update !== first);

      if (newBaseQueueLast === null) {
        newBaseState = newState;
      } else {
        newBaseQueueLast.next = (newBaseQueueFirst: any);
      }

      // Mark that the fiber performed work, but only if the new state is
      // different from the current state.
      if (!is(newState, hook.memoizedState)) {
        markWorkInProgressReceivedUpdate();
      }

      hook.memoizedState = newState;
      hook.baseState = newBaseState;
      hook.baseQueue = newBaseQueueLast;

      queue.lastRenderedState = newState;
    }
		// 返回新的newState和dispatch
    const dispatch: Dispatch<A> = (queue.dispatch: any);
    return [hook.memoizedState, dispatch];
}

此函数的流程可以用一句话概括,获取Hook,并根据Hook,计算出最新的state

获取Hook的函数为updateWorkInProgressHook,此函数和mountWorkInProgressHook有些区别,主要区别在与mount阶段的Hooks是创建得到,而这个是根据渲染树获取的。

注意useReducer中的action是每次都可以修改的queue.lastRenderedReducer = reducer;

触发更新

触发更新的函数为dispatchAction,在调用此函数的时,当前FunctionCompoent对应的FiberHooks对应的queue已经通过bind绑定了参数中。

newQueue.dispatch = setSnapshot = (dispatchAction.bind(
  null,
  currentlyRenderingFiber,
  newQueue,
): any);

在React中大量使用bind进行柯里化,优化传参

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
    // 获取时间
    const eventTime = requestEventTime();
    // 获取优先级
    const lane = requestUpdateLane(fiber);
		
    // 创建update
    const update: Update<S, A> = {
      lane,
      action,
      eagerReducer: null,
      eagerState: null,
      next: (null: any),
    };

    // 将update形成环装链表。
    // 注意此处的queue为hooks.queue
    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;
		// 获取当前workInProgress
    const alternate = fiber.alternate;
    if (
      fiber === currentlyRenderingFiber ||
      (alternate !== null && alternate === currentlyRenderingFiber)
    ) {
      // 表示当前为更新过程中触发的更新
      didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    } else {
      // 当第一次更新时的优化
      if (
        fiber.lanes === NoLanes &&
        (alternate === null || alternate.lanes === NoLanes)
      ) {
        const lastRenderedReducer = queue.lastRenderedReducer;
        if (lastRenderedReducer !== null) {
          let prevDispatcher;
          try {
            const currentState: S = (queue.lastRenderedState: any);
            const eagerState = lastRenderedReducer(currentState, action);
            update.eagerReducer = lastRenderedReducer;
            update.eagerState = eagerState;
            if (is(eagerState, currentState)) {
              return;
            }
          } catch (error) {
          } finally {
            if (__DEV__) {
              ReactCurrentDispatcher.current = prevDispatcher;
            }
          }
        }
      }
      
      // 调度起更新
      scheduleUpdateOnFiber(fiber, lane, eventTime);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
}

在此函数中,有以下几点需要注意

  • currentlyRenderingFiber:表示workInProgress,只有在更新阶段才存在,dispatchAction还未到更新阶段,如果不是在渲染中触发的更新,currentlyRenderingFiber肯定为null
  • 第一次触发更新时,会有个优化

useMemo 和 useCallback

首先区别下这两个Hooks作用:

  • useMemo:是根据依赖来缓存值,只有在依赖发生变化的时候,才会产生新的值。
  • useCallback:根据依赖来缓存函数,只有在依赖发生变化时,才会返回新的函数,可以优化效率

这两个函数在所有的Hooks中算是最简单的两个Hook

mount

function mountMemo<T>(
nextCreate: () => T,
 deps: Array<mixed> | void | null,
): T {
  // 获取当前的Hooks
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  // 将value和nextDeps保存在memoizedState中
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}


function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 获取当前的Hooks
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 保存callback和nextDeps
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

由代码可以看出,mountMemomountCallback唯一的却别在于,mountMemo会根据nextCreate计算出value,保存在memoizedState中,而mountCallback直接将callback保存在memoizedState

update

function updateMemo<T>(
nextCreate: () => T,
 deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      // 对比deps是否相等,如相等直接放回prevState[0]
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
	// 如果不想等,根据nextCreate创建新的value
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
			// 对比deps是否相等,如果相等,直接放回
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  // 创建新的memoizedState
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

useRef

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

useRef的实现相当简单:

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  if (__DEV__) {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  return ref;
}

function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

由代码可以看出,useRef的实现相当简单。

useEffect 和 useLayoutEffect

useEffectuseLayoutEffect都是执行副作用的Hooks,只是执行的时机不同。

componentDidMountcomponentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同。

虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect。

mount

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    HookPassive,
    create,
    deps,
  );
}

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


有代码可以看出,useEffectuseLayoutEffect都是调用mountEffectImpl函数,useEffect传入的参数为HookPassive,而useLayoutEffectHookLayout

那么让我们来看看mountEffectImpl的实现:

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}

function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: (null: any),
  };
  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;
    }
  }
  return effect;
}

mountEffectImpl函数中所做的只是,获取hook,通过pushEffect函数将effect挂载到hoook.memoizedState上。

pushEffect所做的只是将effect形成一个环状链表,并将最后一个节点,返回。

注意:effect的数据结构中包含:tag(类型),create(effect函数),destroy(effect函数的返回函数),deps(依赖性),next(下一个)

update

mount阶段想同,useEffectuseLayoutEffect想同,底层都是调用updateEffectImpl函数,唯一却别的就是传参不同。

function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;
	
  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;
    // 判断deps
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 相同
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;
	// deps 不相同
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}

从上面代码,不知道同学会不会产生一个疑问,什么deps相同的情况也进行了了pushEffect?有代码可以看出deps相同和不相同两次pushEffect的参数是不同

  • 相同:hookFlags
  • 不相同: HookHasEffect | hookFlags

这是为什么呢?

我们都知道,effect是保存在一个环状链表中的,这个链表中元素的个数和位置不能发生变换的,如果发生变化就可能导致一些错误出现,所以React为了简化检查,无论什么情况都将effect加入到链表中,在执行阶段根据tag中是否含有HookHasEffect,进行判读是否执行。

执行流程

在上面的章节我们讲过,在commit阶段的commitBeforeMutationEffects函数中,会调度effect

if ((flags & Passive) !== NoFlags) {
  // If there are passive effects, schedule a callback to flush at
  // the earliest opportunity.
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    // 调度effect
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}

而在commit的layout阶段(commitLifeCycles)中,将要执行的effect加入到数组中,等待执行

function schedulePassiveEffects(finishedWork: Fiber) {
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      const {next, tag} = effect;
      if (
        (tag & HookPassive) !== NoHookEffect &&
        (tag & HookHasEffect) !== NoHookEffect
      ) {
        // 将effect加入到数组中,等待被调度执行
        enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
        enqueuePendingPassiveHookEffectMount(finishedWork, effect);
      }
      effect = next;
    } while (effect !== firstEffect);
  }
}

export function enqueuePendingPassiveHookEffectMount(
  fiber: Fiber,
  effect: HookEffect,
): void {
  pendingPassiveHookEffectsMount.push(effect, fiber);
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}

export function enqueuePendingPassiveHookEffectUnmount(
  fiber: Fiber,
  effect: HookEffect,
): void {
  pendingPassiveHookEffectsUnmount.push(effect, fiber);
  if (__DEV__) {
    fiber.flags |= PassiveUnmountPendingDev;
    const alternate = fiber.alternate;
    if (alternate !== null) {
      alternate.flags |= PassiveUnmountPendingDev;
    }
  }
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}

由代码可以看出,effect会被加入到两个数组中,pendingPassiveHookEffectsUnmountpendingPassiveHookEffectsMount,这两个数组会在flushPassiveEffects被调用。

flushPassiveEffects

flushPassiveEffects函数最终实现为flushPassiveEffectsImpl

function flushPassiveEffectsImpl() {
    if (rootWithPendingPassiveEffects === null) {
      return false;
    }
  
    const root = rootWithPendingPassiveEffects;
    const lanes = pendingPassiveEffectsLanes;
    rootWithPendingPassiveEffects = null;
    pendingPassiveEffectsLanes = NoLanes;

    if (enableSchedulingProfiler) {
      markPassiveEffectsStarted(lanes);
    }
  
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    const prevInteractions = pushInteractions(root);
  
    // First pass: Destroy stale passive effects.
    // 上次执行的useEffect的销毁函数
    const unmountEffects = pendingPassiveHookEffectsUnmount;
    pendingPassiveHookEffectsUnmount = [];
    for (let i = 0; i < unmountEffects.length; i += 2) {
      const effect = ((unmountEffects[i]: any): HookEffect);
      const fiber = ((unmountEffects[i + 1]: any): Fiber);
      const destroy = effect.destroy;
      effect.destroy = undefined;
  
     
      // 如果destroy不为undefined,执行销毁函数
      if (typeof destroy === 'function') {
          destroy();
      }
    }
    // Second pass: Create new passive effects.
    // 执行本次的useEffect
    const mountEffects = pendingPassiveHookEffectsMount;
    pendingPassiveHookEffectsMount = [];
    for (let i = 0; i < mountEffects.length; i += 2) {
      const effect = ((mountEffects[i]: any): HookEffect);
      const fiber = ((mountEffects[i + 1]: any): Fiber);
      effect.destroy = create();
    }
}

我们省略一部分代码,可以看出,此函数主要做两件事:

  • 通过pendingPassiveHookEffectsUnmount调用destroy函数
  • 通过pendingPassiveHookEffectsMount调用create函数

至此Hooks章节完成,concurrent模式我们在react18后补全。