在上一节我们实现了一个极简的useState,了解了Hooks的运行原理。

本节我们讲解Hooks的数据结构,为后面介绍具体的hook打下基础。

dispatcher

在上一节的极简useState实现中,使用isMount变量区分mountupdate

在真实的Hooks中,组件mount时的hookupdate时的hook来源于不同的对象,这类对象在源码中被称为dispatcher

  1. // mount时的Dispatcher
  2. const HooksDispatcherOnMount: Dispatcher = {
  3. useCallback: mountCallback,
  4. useContext: readContext,
  5. useEffect: mountEffect,
  6. useImperativeHandle: mountImperativeHandle,
  7. useLayoutEffect: mountLayoutEffect,
  8. useMemo: mountMemo,
  9. useReducer: mountReducer,
  10. useRef: mountRef,
  11. useState: mountState,
  12. // ...省略
  13. };
  14. // update时的Dispatcher
  15. const HooksDispatcherOnUpdate: Dispatcher = {
  16. useCallback: updateCallback,
  17. useContext: readContext,
  18. useEffect: updateEffect,
  19. useImperativeHandle: updateImperativeHandle,
  20. useLayoutEffect: updateLayoutEffect,
  21. useMemo: updateMemo,
  22. useReducer: updateReducer,
  23. useRef: updateRef,
  24. useState: updateState,
  25. // ...省略
  26. };

可见,mount时调用的hookupdate时调用的hook其实是两个不同的函数。

FunctionComponent render前,会根据FunctionComponent对应fiber的以下条件区分mountupdate

  1. current === null || current.memoizedState === null

并将不同情况对应的dispatcher赋值给全局变量ReactCurrentDispatchercurrent属性。

  1. ReactCurrentDispatcher.current =
  2. current === null || current.memoizedState === null
  3. ? HooksDispatcherOnMount
  4. : HooksDispatcherOnUpdate;

你可以在这里Hooks数据结构 - 图1 (opens new window)看到这行代码

FunctionComponent render时,会从ReactCurrentDispatcher.current(即当前dispatcher)中寻找需要的hook

换言之,不同的调用栈上下文为ReactCurrentDispatcher.current赋值不同的dispatcher,则FunctionComponent render时调用的hook也是不同的函数。

除了这两个dispatcher,你可以在这里Hooks数据结构 - 图2 (opens new window)看到其他dispatcher定义

一个dispatcher使用场景

当错误的书写了嵌套形式的hook,如:

  1. useEffect(() => {
  2. useState(0);
  3. })

此时ReactCurrentDispatcher.current已经指向ContextOnlyDispatcher,所以调用useState实际会调用throwInvalidHookError,直接抛出异常。

  1. export const ContextOnlyDispatcher: Dispatcher = {
  2. useCallback: throwInvalidHookError,
  3. useContext: throwInvalidHookError,
  4. useEffect: throwInvalidHookError,
  5. useImperativeHandle: throwInvalidHookError,
  6. useLayoutEffect: throwInvalidHookError,
  7. // ...省略

你可以在这里Hooks数据结构 - 图3 (opens new window)看到这段逻辑

Hook的数据结构

接下来我们学习hook的数据结构。

  1. const hook: Hook = {
  2. memoizedState: null,
  3. baseState: null,
  4. baseQueue: null,
  5. queue: null,
  6. next: null,
  7. };

你可以在这里Hooks数据结构 - 图4 (opens new window)看到创建hook的逻辑

其中除memoizedState以外字段的意义与上一章介绍的updateQueue类似。

memoizedState

注意

hookFunctionComponent fiber都存在memoizedState属性,不要混淆他们的概念。

  • fiber.memoizedStateFunctionComponent对应fiber保存的Hooks链表。

  • hook.memoizedStateHooks链表中保存的单一hook对应的数据。

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

  • useState:对于const [state, updateState] = useState(initialState)memoizedState保存state的值

  • useReducer:对于const [state, dispatch] = useReducer(reducer, {});memoizedState保存state的值

  • useEffect:memoizedState保存包含useEffect回调函数依赖项等的链表数据结构effect,你可以在这里Hooks数据结构 - 图5 (opens new window)看到effect的创建过程。effect链表同时会保存在fiber.updateQueue

  • useRef:对于useRef(1)memoizedState保存{current: 1}

  • useMemo:对于useMemo(callback, [depA])memoizedState保存[callback(), depA]

  • useCallback:对于useCallback(callback, [depA])memoizedState保存[callback, depA]。与useMemo的区别是,useCallback保存的是callback函数本身,而useMemo保存的是callback函数的执行结果

有些hook是没有memoizedState的,比如:

  • useContext