React Hooks

148 阅读16分钟

目录

前言

本文是React源码学习系列第七篇,该系列整体都基于React18.0.0版本源码。旨在学习过程中记录一些个人的理解。该篇介绍React Hooks。

FC组件入口

function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  // ...
  // 根据执行环境给ReactCurrentDispatcher.current赋值。
  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  let children = Component(props, secondArg);
  // render完以后更改ReactCurrentDispatcher.current指向
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  // ...
}

dispatcher

React Hooks中,组件“mount时的hook”与“update时的hook”来源于不同的对象,这类对象被称为dispatcher。

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,

  unstable_isNewReconciler: enableNewReconciler,
};
//更新时候的hooks
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useSyncExternalStore: updateSyncExternalStore,
  useId: updateId,
};

在进入FC的render流程前,会根据“FC对应的fiberNode的如下判断条件”为ReactCurrentDispatcher.current赋值。其中current.memoizedState保存FC组件的第一个hoook,在FC组件render时,可以从ReactCurrentDispatcher.current中获取“当前上下文环境Hooks对应的实现”。

ReactCurrentDispatcher.current =
  current === null || current.memoizedState === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;

这样设计的目的是检测“Hooks执行的上下文环境”。考虑如下情况,当错误地书写了嵌套形式的hook。

useEffect(() => {
    useState(0);
})

此时ReactCurrentDispatcher.current已经指向ContextOnlyDispatcher,所以执行useState方法时,实际执行的是throwInvalidHookError方法。

const ContextOnlyDispatcher: Dispatcher = {
  readContext,

  useCallback: throwInvalidHookError,
  useContext: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  useImperativeHandle: throwInvalidHookError,
  useInsertionEffect: throwInvalidHookError,
  useLayoutEffect: throwInvalidHookError,
  useMemo: throwInvalidHookError,
  useReducer: throwInvalidHookError,
  useRef: throwInvalidHookError,
  useState: throwInvalidHookError,
  useDebugValue: throwInvalidHookError,
  useDeferredValue: throwInvalidHookError,
  useTransition: throwInvalidHookError,
  useMutableSource: throwInvalidHookError,
  useSyncExternalStore: throwInvalidHookError,
  useId: throwInvalidHookError,
};

Hooks数据结构

const hook = {
  memoizedState: null,

  baseState: null,
  baseQueue: null,
  queue: queue,
  // 指向下一个hook
  next: null,
};

fiberNode也有memoizedState字段,它存的是FC组件对应fiberNode的第一个hook。

hook.memoizedState根据不同hook保存的对应的数据。

  1. useState -> state
  2. useReducer -> state
  3. useEffect -> callback, [...deps]
  4. useRef -> {current: initialValue}
  5. useMemo -> [callback(), [...deps]]
  6. useCallback -> [callback, [...deps]]
  7. baseState是参与计算的初始state,如果没有遗留的Upate,那么等于memoizedState,有遗留的Upate会保存中间状态。
  8. baseQueue是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存。
  9. queue是触发更新后,产生的Update会保存在queue.pending。queue.pending指向最后一个Update。queue.pending.next指向第一个Update。
  10. 开始计算state前会把两个链表连接成新的链表。

Hooks执行流程

  1. FC进入render流程前,确定ReactCurrentDispatcher.current指向。
  2. 进入mount流程时,执行mount对应逻辑,方法名一般为“mountXXX”(其中XXX替换为hook名称,如mountState)。
  3. 进入update流程时,执行update对应逻辑,方法名一般为“updateXXX”(其中XXX替换为hook名称,如updateState)。
  4. 其他情况hook执行,依据ReactCurrentDispatcher.current指向做不同处理。

mount流程

function mountXXX() {
  // 获取对应hook 
  const hook = mountWorkInProgressHook();
  // ...
}

mountWorkInProgressHook

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

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

    next: null,
  };
  if (workInProgressHook === null) {
    // currentlyRenderingFiber为当前FC对应fiberNode
    // 第一个hook保存在fiberNode.memoizedState属性上
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 加入已有hooks链表
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

update流程

function updateXXX() {
  // 获取对应hook 
  const hook = updateWorkInProgressHook();
  // ...
}

updateWorkInProgressHook

function updateWorkInProgressHook(): Hook {
  // renderWithHooks方法会设为null,说明是FC组件的第一个hook
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    // update
    if (current !== null) {
      // current FiberNode的第一个hook
      nextCurrentHook = current.memoizedState;
    } else {
      // ???暂时不知道什么情况会走这里
      nextCurrentHook = null;
    }
  } else {
    // 不是第一个hook,直接取链表的下一个hook
    nextCurrentHook = currentHook.next;
  }
  let nextWorkInProgressHook: null | Hook;
  // renderWithHooks方法会设为null,说明是FC组件的第一个hook
  if (workInProgressHook === null) {
    // FiberNode的第一个hook,默认为null
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    // 不是第一个hook,直接取链表的下一个hook
    nextWorkInProgressHook = workInProgressHook.next;
  }
  // 不为null,说明是render阶段发生的更新
  if (nextWorkInProgressHook !== null) {
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else { // 正常情况下
    // hook对不上,说明有在判断里面写hook
    if (nextCurrentHook === null) {
      // 渲染了比之前更多的钩子。
      throw new Error('Rendered more hooks than during the previous render.');
    }

    currentHook = nextCurrentHook;
    // 从currentHook clone属性
    const newHook: Hook = { 
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    if (workInProgressHook === null) {
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}
  1. 正常update流程,会克隆currentHook作为workInProgressHook并返回。
  2. render阶段触发的更新,因为上一轮render阶段已经创建了workInProgressHook,直接返回它。

useState

function mountState<S>(
  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
  // 创建hook
  const hook = mountWorkInProgressHook();
  // 函数类型value
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  // 初始化memoizedState
  hook.memoizedState = hook.baseState = initialState;
  // 初始化hook.queue
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

useReducer

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 创建hook
  const hook = mountWorkInProgressHook();
  // 初始化memoizedState
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  hook.memoizedState = hook.baseState = initialState;
  // 初始化hook.queue
  const queue: UpdateQueue<S, A> = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

对比发现,mount时两个Hook的主要区别主要体现在queue.lastRenderedReducer属性上,其代表“上一次render时使用的reducer”。

  1. useState的lastRenderedReducer为basicStateReducer。
  2. useReducer的lastRenderedReducer为“传入的reducer”参数。

所以,useState可以视为“reducer参数为basicStateReducer的useReducer”。

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

update时useState与useReducer执行同一个函数updateReducer。

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  
  queue.lastRenderedReducer = reducer;

  const current: Hook = (currentHook: any);
  // 上次计算的state,如果没有update因为优先级不够未参与计算,baseQueue与memoizedState相同。
  let baseQueue = current.baseQueue;
  // 本次更新产生的update链表
  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    // 有update因为优先级不够未参与计算。
    if (baseQueue !== null) {
      // 把baseQueue和pendingQueue链接起来
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  // 拼接完后的baseQueue,按顺序执行
  if (baseQueue !== null) {
    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)) { 
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
        // 优先级不足的update 都挂到newBaseQueue下面
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
      } else { // 优先级够
          // 前面有update优先级不够,后面的update就算优先级足够也不能调度
          if (newBaseQueueLast !== null) { 
          const clone: Update<S, A> = {
            lane: NoLane, // 设为同步优先级,下一次调度一定可以执行
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 如果有急迫的状态直接用,setState的时候调度前提前计算好的。参考前一篇文章性能优化eagerState策略。
        if (update.hasEagerState) { 
          newState = ((update.eagerState: any): S);
        } else { 
          // 没有就计算state
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);
    // 没有因为优先级不足而没参与计算的update
    if (newBaseQueueLast === null) { 
      newBaseState = newState;
    } else { 
      // 有优先级不足没参与计算的update,首尾相连
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }
    // state不同,说明有更新
    if (!is(newState, hook.memoizedState)) { 
      // 把didReceiveUpdate设为true,后面就不能命中bailout
      markWorkInProgressReceivedUpdate(); 
    }
    // 如果没有因为优先级不足而没参与计算的update,newState与newBaseState相同
    hook.memoizedState = newState; 
    // 如果有因为优先级不足而没参与计算的update,newBaseState作为下次调度时候计算的基state
    hook.baseState = newBaseState; 
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }
  // 忽略interleaved相关
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}
  1. 拼接baseQueue和pending.Queue。
  2. 遍历baseQueue上的update计算state,遇到优先级不够的,把该update及之后的update都保存在baseQueue上,把中间state保存在baseState。如果所有update都参与了计算,baseState与memoizedState相同。 queue拼接图 118.jpg

dispatch

useState与useReducer的dispatch方法逻辑基本相同,useState 的做了eagerState优化策略判断。关于优化策略可查看前一篇性能优化篇。没有命中eagerState优化策略会走发起调度逻辑。

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  const lane = requestUpdateLane(fiber);
  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };
  if (isRenderPhaseUpdate(fiber)) { // render阶段的更新
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    enqueueUpdate(fiber, queue, update, lane);
    const alternate = fiber.alternate;
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // 判断wip、current的lanes是否为NoLanes
      const lastRenderedReducer = queue.lastRenderedReducer;
      // 上次计算时使用的reducer
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
          const currentState: S = (queue.lastRenderedState: any);
          // 直接计算出state
          const eagerState = lastRenderedReducer(currentState, action);
          // 将计算出来的状态存储在update上。
          update.hasEagerState = true;
          update.eagerState = eagerState;
          // Object.is
          if (is(eagerState, currentState)) {
            // 状态与当前状态相同,直接return
            return;
          }
        }
      }
    }
    // 没有命中优化策略,发起调度
    const eventTime = requestEventTime();
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
  }
}

Effect相关hook

React共有三个Effect相关的hook。

  1. useEffect:commit阶段完成后异步执行,不会阻塞视图渲染。
  2. useLayoutEffect:commit阶段的Layout子阶段同步执行,一般用于“DOM相关操作”。
  3. useinsertionEffect:commit阶段的Mutation子阶段同步执行,无法访问“DOM的引用”,一般用于CSS-in-JS。

数据结构

const effect = {
  tag,
  create,
  destroy,
  deps,
  next: null,
};
  1. tag有三个类型
  2. const Insertion = 0b0010; -> useInsertionEffect
  3. const Layout = 0b0100; -> useLayoutEffect
  4. const Passive = 0b1000; -> useEffect
  5. create 回调函数
  6. destroy 组件销毁时执行函数
  7. deps 依赖项
  8. next字段指向当前Fiber的其他Effect,形成环状列表。
// 这里是tag
useEffect(() => {
    // 这里是create
    return () => {
        // 这里是destroy
    }
},[]) // 这里是deps

115.jpg 整体工作流程分为三个阶段。

申明阶段

mount 与 update分别对应mountEffectImpl 与 updateEffectImpl方法,区别在于update时会比较deps是否变化。

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

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

updateEffectImpl

判断deps是否有变化,有无变化决定是否打上HookHasEffect标记。注意:就算dpes没有变化还是会创建Effect,保持Effect链表中的effect数量、顺序稳定。

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;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 依赖项是否相同-浅比较
      if (areHookInputsEqual(nextDeps, prevDeps)) { 
        hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }
  // 依赖项有更新 打上对应的fiberFlags Passive为 0b00000000000000100000000000
  // flushPassiveEffects里面会判断该flags
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags, // HookHasEffect 表示该hook需要触发Effect
    create,
    destroy,
    nextDeps,
  );
}

pushEffect

组成链表,保存在fiberNode.updateQueue.lastEffect上。commit阶段的三个字阶段会去执行相对应的Effect。

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

调度阶段(useEffect独有)

// 检查整棵Fiber树上是否存在useEffect,有的话安排一个调度异步执行。
  if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
    if (!rootDoesHavePassiveEffects) {
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      // 已默认优先级调度执行useEffect。
      scheduleCallback(NormalSchedulerPriority, () => {
        flushPassiveEffects();
        return null;
      });
    }
  }

由于调度阶段的存在,为了保证下一次commit阶段执行前“本次commit阶段调度的useEffect”都已执行,commit阶段会在入口处执行flushPassiveEffects方法,以保证本次commit阶段执行时,不存在“还在调度中,为执行的useEffect”。

do {
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

flushPassiveEffects方法内会flushSyncCallbacks方法,遍历执行所有同步优先级调度的更新,执行过程中有可能会产生新的useEffect,所以需要放在do while循环中,确保所有useEffect回调都执行完毕。

执行阶段

根据不同的Effect Tag执行对应的Effect回调方法。 具体参考React Commit阶段篇-commitRoot。

useMemo

useMemo用于缓存一个值。

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
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) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 浅比较依赖项
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  // 更新缓存的数据
  hook.memoizedState = [nextValue, nextDeps];
  // 返回新的数据
  return nextValue;
}

useCallback

useCallback用于缓存一个函数。

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
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) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 浅比较依赖项
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  // 更新缓存的数据
  hook.memoizedState = [callback, nextDeps]; 
  // 返回新的数据
  return callback;
}

mount时useMemo和useCallback唯一的区别是useMemo会执行传入的方法,缓存得到的返回值。useCallback直接缓存传入的方法。 update时大致跟mount差不多,增加了deps是否变化的判断逻辑,如果没有变化,直接返回缓存的值,有变化则更新缓存值,并返回新的值。

useRef

ref用于保存任何”需要被引用的数据,包括不限于DOM元素“。

  1. 函数类型
  2. {current: T}
  3. String类型,已不推荐使用。
function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}
function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

React.createRef也是一样的。

function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  return refObject;
}

本质就是缓存了一个对象引用。

ref工作流程

  1. render阶段beginWork方法内会打上Ref标记。
  2. commit阶段Mutaition子阶段会对有Ref标记的fiberNode重置为null。
  3. commit阶段Layout子阶段会对有Ref标记的fiberNode重新赋上新值
// 保留核心代码
function markRef(current: Fiber | null, workInProgress: Fiber) {
  const ref = workInProgress.ref;
  if (
    (current === null && ref !== null) ||
    (current !== null && current.ref !== ref)
  ) {
    // 打上ref标记
    workInProgress.flags |= Ref;
  }
}
// 保留关键代码
function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
      currentRef(null);
    } else {
      currentRef.current = null;
    }
  }
}
// 保留关键代码
function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostComponent:
        // 浏览器端是返回自己,估计是为了兼容多端
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }
    if (typeof ref === 'function') {
      // 函数方式设置的ref ref={(ref)=> this.ref = ref}
      let retVal = ref(instanceToUse);
    } else {
      // ref = {ref}
      ref.current = instanceToUse;
    }
  }
}

useImperativeHandle

可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,。 useImperativeHandle 应当与 forwardRef 一起使用。

function FancyInput(props, ref) { 
    const inputRef = useRef(); 
    useImperativeHandle(ref, () => ({
        focus: () => { inputRef.current.focus(); 
    }})); 
    return <input ref= {inputRef} ... />; 
} 
// 转发ref
FancyInput = forwardRef(FancyInput);

<FancyInput ref={inputRef} / >
function mountImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const effectDeps =
    deps !== null && deps !== undefined ? deps.concat([ref]) : null;

  let fiberFlags: Flags = UpdateEffect;
  return mountEffectImpl(
    fiberFlags,
    HookLayout,
    imperativeHandleEffect.bind(null, create, ref),
    effectDeps,
  );
}
function updateImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const effectDeps =
    deps !== null && deps !== undefined ? deps.concat([ref]) : null;
  return updateEffectImpl(
    UpdateEffect,
    HookLayout,
    imperativeHandleEffect.bind(null, create, ref),
    effectDeps,
  );
}

跟Effect一样调用updateEffectImpl,区别是Effect Hook第三个参数是传入的create方法。useImperativeHandle是自己实现了一个Effect包装了传入的create方法。

function imperativeHandleEffect<T>(
  create: () => T,
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
) {
  if (typeof ref === 'function') {
    const refCallback = ref;
    const inst = create();
    refCallback(inst);
    return () => {
      refCallback(null);
    };
  } else if (ref !== null && ref !== undefined) {
    const refObject = ref;
    const inst = create();
    refObject.current = inst;
    return () => {
      refObject.current = null;
    };
  }
}

useTransition

useTransition用于“以较低优先级调度一个更新”。 useTransition的实现很简单,由useState 与 startTransition方法组合构成,内部维护了一个状态isPending。

例子

function App() {
    let [num, setNum] = useState(0);
    const [isPending, startTransition] = useTransition();
    return (
        <div style={{color: isPending ? 'red': 'black'}} onClick={() => {
            update(22222);
            startTransition(() =>  update(44444))
        }}>
            {num}
        </div>
    )
}

快速多次点击div后,视图中会闪现红色的“22222”,并最终显示黑色的“44444”。

实现

function mountTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const [isPending, setPending] = mountState(false);
  const start = startTransition.bind(null, setPending);
  const hook = mountWorkInProgressHook();
  hook.memoizedState = start;
  return [isPending, start];
}
function updateTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const [isPending] = updateState(false);
  const hook = updateWorkInProgressHook();
  const start = hook.memoizedState;
  return [isPending, start];
}
function startTransition(setPending, callback, options) {
  // 缓存之前更新优先级
  const previousPriority = getCurrentUpdatePriority();
  // 设置更新优先级
  setCurrentUpdatePriority(
    higherEventPriority(previousPriority, ContinuousEventPriority),
  );
  // 触发isPending状态更新
  setPending(true);
    
  // 缓存之前transition上下文
  const prevTransition = ReactCurrentBatchConfig.transition;
  // 设置当前transition上下文
  ReactCurrentBatchConfig.transition = {};
  const currentTransition = ReactCurrentBatchConfig.transition;
  try {
    // 触发isPending状态更新
    setPending(false);
    // 执行回调函数
    callback();
  } finally {
    // 恢复之前优先级及transition上下文
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

调度前生成update时会去生成一个lane,里面判断了是否在transition上下文。

const NoTransition = null;
// 预留16个TransitionLane
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*  */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*  */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*  */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*  */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*  */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*  */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*  */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*  */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*  */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
function requestUpdateLane(fiber: Fiber): Lane {
  // 是否transition上下文
  const isTransition = requestCurrentTransition() !== NoTransition;
  // 是transition上下文
  if (isTransition) {
    if (currentEventTransitionLane === NoLane) {
      // 拿到下一个空闲的TransitionLane
      currentEventTransitionLane = claimNextTransitionLane();
    }
    return currentEventTransitionLane;
  }
}

function claimNextTransitionLane(): Lane {
  // 循环分配过渡车道
  const lane = nextTransitionLane;
  nextTransitionLane <<= 1;
  // 如果是lane超过过渡TransitionLanes最高位了
  if ((nextTransitionLane & TransitionLanes) === 0) { 
    // 赋值为第一位
    nextTransitionLane = TransitionLane1; 
  }
  return lane;
}

本质是代码内部手动降低了优先级。

useDeferredValue

useTransition为开发者提供了“操作优先级”的功能。开发者可以自行决定“哪些状态更新是‘低优先级更新’”。但有时开发者可能无法访问“触发状态更新的方法”(比如使用第三方库时),此时可以用useDeferredValue。 useDeferredValue接受一个状态并返回“该状态的拷贝”。当原始状态变化后,拷贝对象会以较低优先级更新。useDeferredValue的实现与useTransition类似,都是基于transition上下文。

function mountDeferredValue<T>(value: T): T {
  const [prevValue, setValue] = mountState(value);
  mountEffect(() => {
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = {};
    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}
function updateDeferredValue<T>(value: T): T {
  const [prevValue, setValue] = updateState(value);
  updateEffect(() => {
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = {};
    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}

总结

该片介绍了React常用的几个Hook的用法及实现原理。

参考

React设计原理 - 卡颂