当函数组件进入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函数的解说奠定了根底。

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