乐趣区

关于javascript:React-hooks-的基础概念hooks链表

当函数组件进入 render 阶段时,会被 renderWithHooks 函数解决。函数组件作为一个函数,它的渲染其实就是函数调用,而函数组件又会调用 React 提供的 hooks 函数。初始挂载和更新时,所用的 hooks 函数是不同的,比方首次挂载时调用的 useEffect,和后续更新时调用的 useEffect,尽管都是同一个 hook,然而因为在两个不同的渲染过程中调用它们,所以实质上他们两个是不一样的。这种不一样来源于函数组件要保护一个 hooks 的链表,首次挂载时要创立链表,后续更新的时候要更新链表。

分属于两个过程的 hook 函数会在各自的过程中被赋值到 ReactCurrentDispatcher 的 current 属性上。所以在调用函数组件之前,事不宜迟是依据以后所处的阶段来决定 ReactCurrentDispatcher 的 current,这样才能够在正确的阶段调用到正确的 hook 函数。

export function renderWithHooks<Props, SecondAwrg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  
  // 辨别是挂载还是更新过程,获取不同的 hooks 函数汇合
  ReactCurrentDispatcher.current =
        current === null || current.memoizedState === null
          ? HooksDispatcherOnMount
          : HooksDispatcherOnUpdate;
  // 调用函数组件,let children = Component(props, secondArg);
  ...
  return children;
}

HooksDispatcherOnMountHooksDispatcherOnUpdate,它们外部的 hooks 函数是不同的实现,区别之一在于不同阶段对于 hooks 链表的解决是不同的。

const HooksDispatcherOnMount: Dispatcher = {
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  ...
};

const HooksDispatcherOnUpdate: Dispatcher = {
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  ...
};

意识 hooks 链表

无论是首次挂载还是更新,每调用一次 hooks 函数,都会产生一个 hook 对象与之对应。以下是 hook 对象的构造。

{
    baseQueue: null,
    baseState: 'hook1',
    memoizedState: null,
    queue: null,
    next: {
        baseQueue: null,
        baseState: null,
        memoizedState: 'hook2',
        next: null
        queue: null
    }
}

产生的 hook 对象顺次排列,造成链表存储到函数组件 fiber.memoizedState 上。在这个过程中,有一个非常重要的指针:workInProgressHook,它通过记录以后生成(更新)的 hook 对象,能够间接反映在组件中以后调用到哪个 hook 函数了。每调用一次 hook 函数,就将这个指针的指向移到该 hook 函数产生的 hook 对象上。例如:

const HooksExp = () => {const [ stateHookA, setHookA] = useState('A')
    useEffect(() => { console.log('B') })
    const [stateHookC, setHookC] = useState('C')
    return <div>Hook Example</div>
}

下面的例子中,HooksExp 组件内一共调用了三个 hooks 函数,别离是 useState、useEffect、useState。那么构建 hook 链表的过程,能够概括为上面这样,重点关注 workInProgressHook 的指向变动。

调用 useState(‘A’):

fiber.memoizedState: hookA
                       ^
                workInProgressHook

调用 useEffect:

fiber.memoizedState: hookA -> hookB
                                ^
                         workInProgressHook

调用 useState(‘C’):

fiber.memoizedState: hookA -> hookB -> hookC
                                         ^
                                 workInProgressHook

hook 函数每次执行,都会创立它对应的 hook 对象,去进行下一步的操作,比方 useReducer 会在 hook 对象上挂载更新队列,useEffect 会在 hook 对象上挂载 effect 链表。而创立 hook 对象的过程实际上也是 hooks 链表构建以及 workInProgressHook 指针指向更新的过程。

组件挂载

首次挂载时,组件上没有任何 hooks 的信息,所以,这个过程次要是在 fiber 上创立 hooks 链表。挂载调用的是mountWorkInProgressHook,它会创立 hook 并将他们连接成链表,同时更新 workInProgressHook,最终返回新创建的 hook,也就是 hooks 链表。

function mountWorkInProgressHook(): Hook {
  // 创立 hook 对象
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };

  if (workInProgressHook === null) {
    // workInProgressHook 为 null 阐明此时还没有 hooks 链表,// 将新 hook 对象作为第一个元素挂载到 fiber.memoizedState,// 并将 workInProgressHook 指向它。currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // workInProgressHook 不为 null 阐明曾经有 hooks 链表,此时将
    // 新的 hook 对象连接到链表后边,并将 workInProgressHook 指向它。workInProgressHook = workInProgressHook.next = hook;
  }
  // 返回的 workInProgressHook 即为新创建的 hook
  return workInProgressHook;
}

currentlyRenderingFiber 就是 workInProgress 节点

咱们在组件中调用 hook 函数,就能够获取到 hook 对象,例如 useState:

const HooksDispatcherOnMount: Dispatcher = {
  ...
  useState: mountState,
  ...
};

function mountState<S>(initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // 获取 hook 对象
  const hook = mountWorkInProgressHook();

  // 对 hook 对象的解决
  ...
  return [hook.memoizedState, dispatch];
}

组件更新

在更新过程中,因为存在 current 树,所以 workInProgress 节点也就有对应的 current 节点。那么天然也会有两条 hooks 链表,别离存在于 current 和 workInProgress 节点的 memorizedState 属性上。鉴于此,更新过程的 hooks 链表构建须要另一个指针的参加:currentHook。它作为组件的 workInProgressHook 在上一次更新时对应的 hook 对象,新的 hook 对象能够基于它创立。另外,也能够获取到上次 hook 对象的一些数据,例如 useEffect 的前后依赖项比拟,前一次的依赖项就能够通过它取得。

                currentTree

       current.memoizedState = hookA -> hookB -> hookC
                                          ^             
                                      currentHook
                                          |
         workInProgress Tree              |
                                          |                                
workInProgress.memoizedState = hookA -> hookB
                                          ^          
                                 workInProgressHook

所以更新过程的 hooks 链表构建过程除了更新 workInProgressHook 指针的指向,还要更新 currentHook 的指向,以及尽可能复用 currentHook 来创立新的 hook 对象。
这个过程调用的是 updateWorkInProgressHook 函数:

function updateWorkInProgressHook(): Hook {
  // 确定 nextCurrentHook 的指向
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    // currentHook 在函数组件调用实现时会被设置为 null,// 这阐明组件是刚刚开始从新渲染,刚刚开始调用第一个 hook 函数。// hooks 链表为空
    const current = currentlyRenderingFiber.alternate;
    
    if (current !== null) {
      // current 节点存在,将 nextCurrentHook 指向 current.memoizedState
      nextCurrentHook = current.memoizedState;
    } else {nextCurrentHook = null;}
  } else {
    // 这阐明曾经不是第一次调用 hook 函数了,// hooks 链表曾经有数据,nextCurrentHook 指向以后的下一个 hook
    nextCurrentHook = currentHook.next;
  }
  // 确定 nextWorkInProgressHook 的指向
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    // workInProgress.memoizedState 在函数组件每次渲染时都会被设置成 null,// workInProgressHook 在函数组件调用实现时会被设置为 null,// 所以以后的判断分支阐明当初正调用第一个 hook 函数,hooks 链表为空
    // 将 nextWorkInProgressHook 指向 workInProgress.memoizedState,为 null
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    // 走到这个分支阐明 hooks 链表曾经有元素了,将 nextWorkInProgressHook 指向
    // hooks 链表的下一个元素
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // 根据下面的推导,nextWorkInProgressHook 不为空阐明 hooks 链表不为空
    // 更新 workInProgressHook、nextWorkInProgressHook、currentHook
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // 走到这个分支阐明 hooks 链表为空
    // 刚刚调用第一个 hook 函数,基于 currentHook 新建一个 hook 对象,invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

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

      next: null,
    };
    
    // 根据状况构建 hooks 链表,更新 workInProgressHook 指针
    if (workInProgressHook === null) {currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;} else {workInProgressHook = workInProgressHook.next = newHook;}
  }
  return workInProgressHook;
}

总结

通过本文咱们理解到,函数组件内的 hooks 函数调用是会造成一个 hooks 链表的,这个链表会挂载到函数组件对应 fiber 的 memoizedState 属性上。这为咱们后续 hooks 函数的解说奠定了根底。

欢送扫码关注公众号,发现更多技术文章

退出移动版