Skip to content

hooks 实现

beginWork阶段,为了获取函数式组件的children,在updateFunctionComponent函数中会调用renderWithHooks方法:

javascript
nextChildren = renderWithHooks(
  current,
  workInProgress,
  Component,
  nextProps,
  context,
  renderLanes,
);

renderWithHooks方法在ReactFiberHooks.new.js文件中定义,它的关键代码为:

javascript
ReactCurrentDispatcher.current =
  current === null || current.memoizedState === null
  ? HooksDispatcherOnMount
 : HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
ReactCurrentDispatcher.current = ContextOnlyDispatcher;

首先会根据current.memoizedState来判断当前是mount阶段还是update阶段,从而决定使用HooksDispatcherOnMount还是HooksDispatcherOnUpdate。然后执行Component,在执行的过程中,如果我们使用了hook,那么就会执行相应的hook方法。最后将ReactCurrentDispatcher.current置空。下面看一下hooks的实现。

useState

useStateReact包中定义,其他的hooks也同样在这里定义:

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

第一步是resolveDispatcher获取当前的dispatcher。在renderWithHooks函数执行前已经通过判断mount/update将当前的dispatcher存放到全局,所以这里可以获取到。

mount 阶段

如果是mount阶段,useState对应的是mountState方法:

javascript
const HooksDispatcherOnMount: Dispatcher = {
  useState: mountState,
}

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // 创建一个 hook,挂载到 fiber上,并返回
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  // 初始化 queue
  const queue = (hook.queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<
    BasicStateAction<S>,
    > = (queue.dispatch = (dispatchAction.bind(
      null,
      currentlyRenderingFiber,
      queue,
    ): any));
  return [hook.memoizedState, dispatch];
}

mountWorkInProgressHook方法

第一步执行的是mountWorkInProgressHook,该函数首先会创建一个hook,用于存放对应hook的状态:

javascript
const hook: Hook = {
  // 记录该 hook 的 state
  memoizedState: null,
  // base state
  baseState: null,
  // base queue
  baseQueue: null,
  // 更新queue
  queue: null,
  // 指向下一个 hook
  next: null,
};

随后将hook形成链表结构,并将该链表存放到fiber.memoizedState当中:

javascript
if (workInProgressHook === null) {
  // currentlyRenderingFiber 表示的是当前正在执行的的 函数 fiber。
  currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
  // 遇到的 hooks 形成链表形式。
  workInProgressHook = workInProgressHook.next = hook;
}

这样后续就可以通过链表来访问每一个使用到的hook的状态了。

dispatchAction

在拿到hook后会初始化memoizedState,并将相应的memoizedStatedispatch函数返回。当调用dispatch函数时,实际触发的是dispatchAction函数,首先会创建一个update:

javascript
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);

const update: Update<S, A> = {
  lane,
  action,
  // update.eagerReducer = lastRenderedReducer;
  eagerReducer: null,
  // update.eagerState = lastRenderedReducer(currentState, action);
  eagerState: null,
  next: (null: any),
};

如果当前正在处理这个fiber,触发了dispatch,那么会执行如下分支:

javascript
if (
  fiber === currentlyRenderingFiber ||
  (alternate !== null && alternate === currentlyRenderingFiber)
) {
  // 注意这个参数用于防止循环渲染
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  const pending = queue.pending;
  // 将 update 形成链表形式
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

此时会将update形成循环链表,存放到queue.pending当中,并将didScheduleRenderPhaseUpdateDuringThisPass标记为true,这个标记在renderWithHooks中有用到:

javascript
if (didScheduleRenderPhaseUpdateDuringThisPass) {
  let numberOfReRenders: number = 0;
  do {
    didScheduleRenderPhaseUpdateDuringThisPass = false;
    invariant(
      numberOfReRenders < RE_RENDER_LIMIT,
      'Too many re-renders. React limits the number of renders to prevent ' +
      'an infinite loop.',
    );

    numberOfReRenders += 1;

    currentHook = null;
    workInProgressHook = null;
    workInProgress.updateQueue = null;


    ReactCurrentDispatcher.current = __DEV__
      ? HooksDispatcherOnRerenderInDEV
      : HooksDispatcherOnRerender;

    children = Component(props, secondArg);
  } while (didScheduleRenderPhaseUpdateDuringThisPass);
}

如果为true的话,在函数组件执行完成后,会将dispatcher改为HooksDispatcherOnRerender并再次执行Component函数。并且这个重新渲染的次数不能大于最大次数RE_RENDER_LIMIT

如果触发时的fiber与当前fiber不相等时,同样会建立循环链表的update存放到queue.pending当中,不同的是,此时会进行schedule

javascript
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);

rerender 阶段

mount阶段我们提到了didScheduleRenderPhaseUpdateDuringThisPass字段为true时,如果当前函数执行完毕,此时会立即触发rerender,使用的是HooksDispatcherOnRerender,因此useState对应于rerenderState也是rerenderReducer函数。

updateWorkInProgressHook

updateWorkInProgressHookmountWorkInProgressHook对应,它是用于取存放在fiber.memoizedState上的hook

javascript
let nextCurrentHook: null | Hook;
if (currentHook === null) {
  const current = currentlyRenderingFiber.alternate;
  if (current !== null) {
    nextCurrentHook = current.memoizedState;
  } else {
    nextCurrentHook = null;
  }
} else {
  nextCurrentHook = currentHook.next;
}

// 这个对应的 fiber 是 workInProgress
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
  nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
  nextWorkInProgressHook = workInProgressHook.next;
}

nextCurrentHookcurrentHook对应于老fiber上的hooks,而nextWorkInProgressHookworkInProgressHook对应于新fiber上的hooks。当新fiber上的hook存在时,直接使用即可:

javascript
if (nextWorkInProgressHook !== null) {
  // 如果 新fiber上存在该 hook, 可以直接使用
  workInProgressHook = nextWorkInProgressHook;
  nextWorkInProgressHook = workInProgressHook.next;

  currentHook = nextCurrentHook;
}

否则会复用老fiber上的hook

javascript
currentHook = nextCurrentHook;

// 如果新的 hook 为 null 时,需要将 current hook 作为 base,
// 进行创建新的 hook
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;
}

更新state

在拿到对应的hook后,就会依次执行所有的update,更新当前的state:

javascript
// 循环执行
do {
  const action = update.action;
  newState = reducer(newState, action);
  update = update.next;
} while (update !== firstRenderPhaseUpdate);

// 更新 state
hook.memoizedState = newState;
if (hook.baseQueue === null) {
  hook.baseState = newState;
}
queue.lastRenderedState = newState;

update 阶段

update阶段调用的是updateReducer,与rerender不同的是,循环执行update队列时,会判断updateLane是否在renderLanes中:

javascript
if (!isSubsetOfLanes(renderLanes, updateLane)) {}

这个阶段的更新与UpdateQueue章节中class组件的processUpdate方法更新类似,这里不再赘述。

useReducer

useReducer的流程就与useState一致,不同的是传入的reducer是用户定义的reducer,并且dispatch的时候会携带action。在useState中,reducerstate => statedispatch的时候无需action

useEffect

mount阶段

mount阶段useEffect调用的是mountEffect

javascript
return mountEffectImpl(
  PassiveEffect | PassiveStaticEffect,
  HookPassive,
  create,
  deps,
)
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,
  );
}

mountWorkInProgressHookuseState中已经讲过,其主要作用是创建一个hook,并将该hook与其他hook形成链表存放到fiber.memoizedState属性上。

currentlyRenderingFiber.flags |= fiberFlags这里表示如果使用了useEffect这个hook,那么fiber.flags上会增加PassivePassiveStatic这两个标识符。

接下来是pushEffect方法,首先会创建一个effect

javascript
const effect: Effect = {
  // 表示 hook 的 tag
  tag,
  // 创建时的回调函数
  create,
  // 销毁时的回调函数
  destroy,
  // hook 的依赖
  deps,
  // 指向下一个 effect
  next: (null: any),
};

effecttagHasEffect | Passive,随后将这个effect与其他的effect形成链表结构存放到fiber.updateQueue中:

javascript
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
// 如果不存在 updateQueue,进行初始化
if (componentUpdateQueue === null) {
  // 建立循环链表
  componentUpdateQueue = createFunctionComponentUpdateQueue();
  currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
  componentUpdateQueue.lastEffect = effect.next = effect;
} else {
  // 如果存在updateQueue,将effect添加到链表中
  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;
  }
}

这里fiber.updateQueue.lastEffect指向的是新添加的effect

rerender和update阶段

rerenderupdate阶段都是调用的updateEffect方法,也是调用的updateEffectImpl方法。首先会获取相应的hook:

javascript
const hook = updateWorkInProgressHook()

随后会判断依赖deps有没有发生变化。如果没有发生变化,那么pushEffect的时候,effect.tags不包含HasEffect。反之,则会包含。

javascript
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);

useLayoutEffect

mount 阶段

useLayoutEffectmount阶段与useEffect几乎一致,主要的区别有两点:

  • fiberflagsUpdate | LayoutStatic
  • effecttagHasEffect | Layout

rerender和update阶段

useEffect一致,主要的区别在于:

  • fiberflagsUpdateEffect
  • effecttagHasEffect | Layout

useMemo

mount 阶段

javascript
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;
}

首先获取hook,然后执行nextCreate并将结果返回。

rerender和update阶段

updateMemo方法主要用于判断前后的deps是否一致。如果一致,则跳过函数执行,将先前的值返回,从而达到缓存的目的。

javascript
if (areHookInputsEqual(nextDeps, prevDeps)) {
  return prevState[0];
}

useCallback

mount 阶段

javascript
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;
}

memoizedState存储的是callbackdeps

rerender和update阶段

对比deps看是否发生变化,如果没变,则将原callback返回。

javascript
if (areHookInputsEqual(nextDeps, prevDeps)) {
  return prevState[0];
}

useContext

mount、rerender和update阶段

useContext三个阶段都是执行的readContext方法,首先会获取到contextvalue值,也就是用户定义的context的值。

javascript
const value = isPrimaryRenderer
  ? context._currentValue
  : context._currentValue2;

随后判断lastFullyObservedContext === context,如果为true,说明该context已经被处理了,无需重复处理,跳过即可。

javascript
if (lastFullyObservedContext === context) {
  // Nothing to do. We already observe everything in this context.
}

否则的话,会创建一个contextItem,形成链表并存放到fiber.dependencies.firstContext上:

javascript
const contextItem = {
  context: ((context: any): ReactContext<mixed>),
  memoizedValue: value,
  next: null,
};

if (lastContextDependency === null) {
  lastContextDependency = contextItem;
  currentlyRenderingFiber.dependencies = {
    lanes: NoLanes,
    firstContext: contextItem,
  };
} else {
  lastContextDependency = lastContextDependency.next = contextItem;
}

这里的处理与class组件的context处理类似。

useRef

mount 阶段

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

mountRef方法创建了一个{ current: xxx }对象并将其返回。

rerender和update阶段

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

updateRef也非常的简单,将hook.memoizedState中存储的{ current: xxx }对象返回。

useTransition

mount 阶段

useTransition的实现是基于useState的:

javascript
function mountTransition(): [boolean, (() => void) => void] {
  const [isPending, setPending] = mountState(false);
  const start = startTransition.bind(null, setPending);
  const hook = mountWorkInProgressHook();
  hook.memoizedState = start;
  return [isPending, start];
}

首先会通过useState定义isPending和setPending方法,然后定义一个start方法并将其返回:

javascript
function startTransition(setPending, callback) {
  const previousPriority = getCurrentUpdatePriority();
  setCurrentUpdatePriority(
    higherEventPriority(previousPriority, ContinuousEventPriority),
  );

  // 添加一个 update,优先级为当前 update 优先级
  setPending(true);
  // 此时进行 schedule 的时候,requestUpdateLane 为原始的 lane
  // 比如在 click 的时候就会是同步 lane
  const prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = 1;
  try {
    // 添加一个 update,requestUpdateLane的结果为 transition lane
    setPending(false);
  // 执行 startTransition 的回调
    callback();
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

首先会设置更新时的优先级,这样在setPending的时候,会进行requestUpdateLane,拿到对应的lane。随后将ReactCurrentBatchConfig.transition置为1,在requestUpdateLane会返回transition lane。因此try内部setPending(false)是一个transition lane的更新。同样callback内部定义的更新也将会是一个transition lane的更新。这样在transition更新的时候,setPending(false)会执行。

rerender和update阶段

javascript
function updateTransition(): [boolean, (() => void) => void] {
  const [isPending] = updateState(false);
  const hook = updateWorkInProgressHook();
  const start = hook.memoizedState;
  return [isPending, start];
}

update阶段比较简单,拿到isPendingstart方法返回即可。

useDeferredValue

mount、rerender和update阶段

useDeferredValue的实现是基于useStateuseEffect的:

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

useEffect内部设置value时,会将ReactCurrentBatchConfig.transition置为1,因此更新对应的lanetransition lane