阅读 690
(建议收藏) React Hooks 源码解毒

(建议收藏) React Hooks 源码解毒

前言

知其所以然的时刻到了。先来道面试题(相信绝大部分React开发童鞋都遇到过)

面试官:function 组件 和 class 组件有什么区别?

答: 有几方面。

  • 使用function创建的组件叫做 「无状态组件,使用class创建的组件叫做 「有状态组件」 
  • 「无状态组件」 只能函数入参 (也就是 props)来接收外界传递过来的数据。 「有状态组件」 除了只读属性 this.props 外, 还有个存放私有数据的 this.state 属性,该属性可读写。 
  • function 没有 this 的困扰。因为你不会也不用在function里面写 this。吼!(破音) 
  • 「有状态组件」 存在生命周期。 「无状态组件」 木有生命周期。 

以上都是废话,「有 / 无 状态组件」最本质的区别在于:在class状态中,通过一个实例化的class ,去维护组件中的各种状态;但是在function组件中,没有一个状态机制去保存这些信息。每一次函数上下文执行,所有变量&常量都重新声明,执行完毕,再被垃圾机制回收。

(这里给面试官埋了坑,你要么继续往下问 react hooks,要么问函数执行上下文,再要么v8 GC。 这叫面试心理学,学着点~ 2333333)

那么,实际「React hooks」也并没有多难理解,说白了就是为 「无状态组件」 提供一套状态管理机制,在此基础上又增加了一些问题的解决方案。例如逻辑不能复用、无法优雅的打破纯函数平衡等。

来吧,热热身

以下问题,你能答好几个。

  1. 在无状态组件每一次函数上下文执行的时候,「React** 用什么方式记录了 「**Hooks**」** 的状态? 
  2. react hooks 」`** 如何记录 每一个 钩子的使用顺序的? 
  3. 为什么不能条件语句中,声明 hooks ? 
  4. function**组件中的 「useState」** 和 「calss」`**组件的setState有什么区别? 
  5. 「useEffect」**、「useMemo」**需要依赖注入,为什么「useRef」`**不需要? 
  6. 「 useMemo 」`** 是如何对组件进行缓存的? 
  7. 为什么多次 调用多次 「useState」`** ,函数组件不更新? 
  8. 能手动实现这些 hooks 吗? 

React Hooks 原理

image.png

一切从使用开始分析:

import { useState } from 'react'


function A () {

    const [xx,setXx] = useState('')

}
复制代码

当你 import 「 useState 」的时候,发生了什么事情? 废话,当然是执行源码去了 ……

Path:  React/cjs/react.development.js

image.png

贴图有点丑,后面就不贴了……

// 源码第1495行

function useState(initialState) {

  var dispatcher = resolveDispatcher();

  return dispatcher.useState(initialState);

}
复制代码

很明显,我们调用的 useState 就是 dispatcher.useState, 而dispatcherresolveDispatcher的执行结果。 so,接着看 resolveDispatcher


function resolveDispatcher() {

  var dispatcher = ReactCurrentDispatcher.current;


  if (!(dispatcher !== null)) {

    {

      throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem." );

    }

  }


  return dispatcher;

}
复制代码

破案1: 为什么hooks 必须要在function内部使用?

源码很好理解,ReactCurrentDispatcher.current 就是当前的 dispatcher 



/**

 * Keeps track of the current dispatcher.

 */

var ReactCurrentDispatcher = {

  /**

   * @internal

   * @type {ReactComponent}

   */

  current: null

};
复制代码

 先来读一下注释。 直译过来是跟踪当前调度程序,意译过来则是: 对当前的调度者保持联系。再看current的字段注释:ReactComponent。 ****意思是,这个调度者必须是React组件。

OK,源码看到这就开始尴尬了。因为没下文了呀,current 初始值是null,然后没了😅。既然 useState  这条路没走通,那就只能从useState的上一层来看了。

function 是如何执行的?

因为function 内部才能使用react hooks 嘛!那我们知道在React 新架构中,什么东西在什么时候调用是由协调层统筹的。所以,我们可以抱着猜想去看看 「react-reconciler」`

Path: react/packages/react-reconciler/src/ReactFiberBeginWork.new.js

在源码中你会看见大量的 「renderWithHooks」调用。看名字你也知道,这个方法就是用来渲染function组件的。


export function renderWithHooks<Props, SecondArg>(

  current: Fiber | null, //  如果是初始化 则 current 为 null

  workInProgress: Fiber, // *workInProgress Fiber*

  Component: (p: Props, arg: SecondArg) => any, // function Component 本身

  props: Props,

  secondArg: SecondArg,

  nextRenderLanes: Lanes, // 下一个渲染通道。 曾经也叫 渲染过期时间

): any {

  renderLanes = nextRenderLanes;

  currentlyRenderingFiber = workInProgress; // &1 *workInProgress*到底干嘛的?我们先给它打个标,待会儿解释


  if (__DEV__) {

    hookTypesDev =

      current !== null

        ? ((current._debugHookTypes: any): Array<HookType>)

        : null;

    hookTypesUpdateIndexDev = -1;

    // Used for hot reloading:

    ignorePreviousDependencies =

      current !== null && current.type !== workInProgress.type;

  }


  workInProgress.memoizedState = null; // &2  缓存state,具体放什么后面说

  workInProgress.updateQueue = null; // &3 更新队列

  workInProgress.lanes = NoLanes;

  if (__DEV__) {

   // code ……

  } else {

    ReactCurrentDispatcher.current =

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

        ? HooksDispatcherOnMount

        : HooksDispatcherOnUpdate;

  }


  let children = Component(props, secondArg); // **Component(props, secondArg)**


  // Check if there was a render phase update

  if (didScheduleRenderPhaseUpdateDuringThisPass /*在此过程中,计划渲染阶段是否更新*/) {

    // Keep rendering in a loop for as long as render phase updates continue to

    // be scheduled. Use a counter to prevent infinite loops.

    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;

      // code ……


      ReactCurrentDispatcher.current = __DEV__

        ? HooksDispatcherOnRerenderInDEV

        : HooksDispatcherOnRerender;

      children = Component(props, secondArg);

    } while (didScheduleRenderPhaseUpdateDuringThisPass);

  }



  // We can assume the previous dispatcher is always this one, since we set it

  // at the beginning of the render phase and there's no re-entrance.

  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

    // code... 源码太多,部分不贴了。

  const didRenderTooFewHooks =

      currentHook !== null && currentHook.next !== null;

    // code... 源码太多,部分不贴了。

  invariant(

    !didRenderTooFewHooks,

    'Rendered fewer hooks than expected. This may be caused by an accidental ' +

      'early return statement.',

  );

  return children;

}

复制代码

呼~! 此处小结一下:

  • renderWithHooks 其实是个高阶函数,最终会return 函数组件本身 
  • 开始执行函数组件,初始化「workInProgress Fiber 树」的「 memoizedState 」和 「updateQueue」。 why
    • 因为在接下来的过程中,要把新的hooks信息(update)挂载到这两个属性上,然后在组件commit阶段,将workInProgress树替换成current树,替换真实的DOM元素节点。并在current树保存hooks信息。 
  • 在函数组件上下文执行阶段,会循环判断在此过程中,计划渲染阶段是否更新(didScheduleRenderPhaseUpdateDuringThisPass)。 hooks被依次执行,把hooks信息依次保存到workInProgress树上(如何保存,后面再讲)。 
  • nextRenderLanes: 用来判定优先级。 
  • 调用Component(props, secondArg) 。函数组件在这里真正的被执行了 
  • 不管什么样的环境下,ReactCurrentDispatcher.current 都会被赋值。初始化则被赋予「HooksDispatcherOnMount」,更新则被赋予「HooksDispatcherOnUpdate」。 

HooksDispatcherOnMount & HooksDispatcherOnUpdate


const HooksDispatcherOnMount: Dispatcher = {

  readContext,

  useCallback: mountCallback,

  useContext: readContext,

  useEffect: mountEffect,

  useImperativeHandle: mountImperativeHandle,

  useLayoutEffect: mountLayoutEffect,

  useMemo: mountMemo,

  useReducer: mountReducer,

  useRef: mountRef,

  useState: mountState,

  useDebugValue: mountDebugValue,

  useDeferredValue: mountDeferredValue,

  useTransition: mountTransition,

  useMutableSource: mountMutableSource,

  useOpaqueIdentifier: mountOpaqueIdentifier,


  unstable_isNewReconciler: enableNewReconciler,

};


const HooksDispatcherOnUpdate: Dispatcher = {

  readContext,


  useCallback: updateCallback,

  useContext: readContext,

  useEffect: updateEffect,

  useImperativeHandle: updateImperativeHandle,

  useLayoutEffect: updateLayoutEffect,

  useMemo: updateMemo,

  useReducer: updateReducer,

  useRef: updateRef,

  useState: updateState,

  useDebugValue: updateDebugValue,

  useDeferredValue: updateDeferredValue,

  useTransition: updateTransition,

  useMutableSource: updateMutableSource,

  useOpaqueIdentifier: updateOpaqueIdentifier,


  unstable_isNewReconciler: enableNewReconciler,

};


复制代码

所以,走到这,很多东西就逐渐明了了。 

  1. renderWithHooks」`** ****调用后,Dispatcher有了。 
  2. react hooks 钩子分 初始化和更新,初始化用mount XXX, 更新则是update XXX。 

OK。再来张图解析一下。

image.png

调用useXXX之后发生了什么

useState

function mountWorkInProgressHook(): Hook {

  const hook: Hook = {

    memoizedState: null,


    baseState: null,

    baseQueue: null,

    queue: null,

    next: null,

  };

  if (workInProgressHook === null) {

    // This is the first hook in the list

    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;

  } else {

    // Append to the end of the list

    **workInProgressHook = workInProgressHook.next = hook;** // &

  }

  return workInProgressHook;

}


function mountState<S>( 

  initialState: (() => S) | S,

): [S, Dispatch<BasicStateAction<S>>] {

  const hook = mountWorkInProgressHook(); // &1 

  if (typeof initialState === 'function') {

    // $FlowFixMe: Flow doesn't like mixed types

    initialState = initialState();

  }

  hook.memoizedState = hook.baseState = initialState;

  const queue = (hook.queue = { // &2 

    pending: null,

    interleaved: null,

    lanes: NoLanes,

    dispatch: null,

    lastRenderedReducer: basicStateReducer,

    lastRenderedState: (initialState: any),

  });

  const dispatch: Dispatch< // &3

    BasicStateAction<S>,

  > = (queue.dispatch = (dispatchAction.bind(

    null,

    currentlyRenderingFiber,

    queue,

  ): any));

  return [hook.memoizedState, dispatch]; // &4 

}

复制代码

解析一下:

  1. 调用 mountWorkInProgressHook(), 得到一个hook对象。 源码直观看大,hook对象上有「memoizedState」、 「baseState」、「queue」 属性。 由 mountWorkInProgressHook 内部实现可知,每个 hook 都以链表形式串联起来,并赋值给 workInProgress 的 memoizedState,从而进一步证实函数组件用 memoizedState 存放 hooks 链表。 
  2. queue」。 更新队列 
  3. dispatch」, 调度,这是个动词。 
  4. return [hook.memoizedState, dispatch]; 最终return 出 你可以结构出来的东西。((const [xxx,setXxx] = useState()) 

这里要仔细看看,你会发现还有些属性不太懂。 Hook 对象中都存放了那些属性?

  • memoizedState: useState 中 保存 state 信息 | useEffect 中 保存着effect 对象 | useMemo 中 保存的是缓存的值和 deps| useRef 中保存的是ref 对象。 
  • baseQueue : useState 和useReducer中 保存最新的更新队列。 
  • baseState : usestate 和 useReducer中,一次更新中 ,产生的最新state值。 
  • queue : 保存待更新队列 pendingQueue ,更新函数 dispatch 等信息。 
  • next: 指向下一个 hooks对象。 

破案二: 为什么不能在 if 等条件语句中声明 hooks

答: 因为 条件语句会破坏 函数组件用 memoizedState 存放 hooks 存放 hooks 的链表结构。你想让next 指向谁?

Q:那dispatchAction 又是什么?

答: 「mountState」究竟做了什么上诉已经说明。但 「dispatchAction」 ……

dispatchAction


function dispatchAction<S, A>(

  fiber: Fiber,

  queue: UpdateQueue<S, A>,

  action: A,

) {

  if (__DEV__) {

    if (typeof arguments[3] === 'function') {

      console.error(

        "State updates from the useState() and useReducer() Hooks don't support the " +

          'second callback argument. To execute a side effect after ' +

          'rendering, declare it in the component body with useEffect().',

      );

    }

  }



  // &1 计算本次渲染通道

  const eventTime = requestEventTime();

  const lane = requestUpdateLane(fiber);

 // &2 声明update

  const update: Update<S, A> = {

    lane,

    action,

    eagerReducer: null,

    eagerState: null,

    next: (null: any),

  };


  //alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber

  const alternate = fiber.alternate; 

  

  // &3 判断当前是否在渲染阶段**

  if (

    fiber === currentlyRenderingFiber ||

    (alternate !== null && alternate === currentlyRenderingFiber)

  ) {

    // This is a render phase update. Stash it in a lazily-created map of

    // queue -> linked list of updates. After this render pass, we'll restart

    // and apply the stashed updates on top of the work-in-progress hook.

    // 似曾相似对吧?

    **didScheduleRenderPhaseUpdateDuringThisPass** = didScheduleRenderPhaseUpdate = true;

    const pending = queue.pending;

    if (pending === null) {

      // This is the first update. Create a circular list.

      update.next = update;

    } else {

      update.next = pending.next;

      pending.next = update;

    }

    queue.pending = update;

  } else { 

  /* 当前函数组件对应*fiber*没有处于调和渲染阶段 **,准备更新 **/*

  

  // &4 判断是否交叉更新

    if (isInterleavedUpdate(fiber, lane)) {

      const interleaved = queue.interleaved;

      if (interleaved === null) {

        // This is the first update. Create a circular list.

        update.next = update;

        // At the end of the current render, this queue's interleaved updates will

        // be transferred to the pending queue.

        // &5 在当前渲染结束时,此队列的交错更新将传输到挂起队列。

        pushInterleavedQueue(queue);

      } else {

        update.next = interleaved.next;

        interleaved.next = update;

      }

      queue.interleaved = update;

    } else {

      const pending = queue.pending;

      if (pending === null) {

        // This is the first update. Create a circular list.

        update.next = update;

      } else {

        update.next = pending.next;

        pending.next = update;

      }

      queue.pending = update;

    }

    if (

      fiber.lanes === NoLanes &&

      (alternate === null || alternate.lanes === NoLanes)

    ) {

    // &6 

    const lastRenderedReducer = queue.lastRenderedReducer;

    if (lastRenderedReducer !== null) {

        let prevDispatcher;

        if (__DEV__) {

          prevDispatcher = ReactCurrentDispatcher.current;

          ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;

        }

        try {

          const currentState: S = (queue.lastRenderedState: any);

          const eagerState = lastRenderedReducer(currentState, action);

        

          update.eagerReducer = lastRenderedReducer;

          update.eagerState = eagerState;

          // &7 

          if (is(eagerState, currentState)) {

    

            return;

          }

        } 

         // code。。。 省略部分源码,写不动了。

      }

      // code。。。 省略部分源码,写不动了。

    }

    // code。。。 省略部分源码,写不动了。
、
    // &8

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

     // code。。。 省略部分源码,写不动了。

}
复制代码

解析一下:

  1. 计算本次渲染通道,判定优先级要用 
  2. 声明 update
    1. Lane : 不解释了 
    2. action : 不解释了 
    3. eagerReducer : 急减速器。 调整优先级方式的一种 
    4. eagerState : 配套使用 
    5. next : 不解释了 
  3. dispatchAction第二步就是判断当前函数组件的fiber对象是否处于渲染阶段,如果处于渲染阶段,那么不需要我们在更新当前函数组件,只需要更新一下当前update的 fiber.lanes 即可。
    1. 无论是类组件调用setState,还是函数组件的dispatchAction ,都会产生一个 update对象,里面记录了此次更新的信息,然后将此update放入待更新的pending队列中。 
  4. 判断是否交叉更新 
  5. 在当前渲染结束时,此队列的交错更新将传输到挂起队列。 
  6. 如果当前fiber没有处于更新阶段。那么通过调用lastRenderedReducer获取最新的eagerState 
  7. 和上一次的currentState,进行浅比较,如果相等就直接return。 

破案三:这就证实了为什么 useState,两次值相等的时候,组件不渲染的原因了。这个机制和 Component 模式下的 setState 有一定的区别。 scheduleUpdateOnFiberreact渲染更新的主要函数。

  1. scheduleUpdateOnFiber」渲染fiber 

Q:为什么 React 当中 ****在 ****非React API当中使用 set XXX 会被立刻更新?

答:正常的都被急减速器减速了,不正常的被pending了。 但这里还牵扯到 「Scheduler 调度层」。 我们目前 还处于 「Reconciler 协调层」。 这个以后再讲……

useEffect


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,

  );

}


function mountEffect(

  create: () => (() => void) | void,

  deps: Array<mixed> | void | null,

): void {

  if (__DEV__) {

    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests

    if ('undefined' !== typeof jest) {

      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);

    }

  }

  if (

    __DEV__ &&

    enableStrictEffects &&

    (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode

  ) {

    return mountEffectImpl(

      MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,

      HookPassive,

      create,

      deps,

    );

  } else {

    return mountEffectImpl(

      PassiveEffect | PassiveStaticEffect,

      HookPassive,

      create,

      deps,

    );

  }

}

复制代码

同理,每个hooks初始化都会创建一个hook对象,然后将 hook 的「memoizedState」保存当前effect hook信息。

Tips:看清楚了,mountEffect 和 mountState的 「memoizedState」存放的东西是不一样的!

所以,hook.memoizedState = pushEffectpushEffect是什么东西?


function pushEffect(tag, create, destroy, deps) {

  const effect: Effect = {

    tag,

    create,

    destroy,

    deps,

    // Circular

    next: (null: any),

  };

  // &1 

  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);

  if (componentUpdateQueue === null) { // &2 

    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;

    }

  }

  // &3

  return effect;

}
复制代码

(相信此时你对如何阅读源码已经有了一定心得。我就不写注释了…… 憋打我)

解析一下

  1. 「pushEffect」 创建effect对象,挂载 updateQueue 
  2. 判断组件如果第一次渲染,那么创建 componentUpdateQueue ,就是workInProgress的updateQueue。然后将effect放入updateQueue中。 
  3. 最终返回 「effect」

Q:React 是如何执行所有的 effect 的?

答:  Fiber对象 上有个effectTag 属性。 React是采取深度优先遍历的方式来便利Fiber 树,每个Fiber对象的effectTag属性,根据Fiber对象的firstEffect 属性,将Fiber对象有效的副作用提取出来 ,构建出一个只包含副作用的 EffectList。 最终遍历EffectList,根据effectTag属性去执行相对应的副作用回调函数。(这里是不是又开始对Fiber 对象好奇了?)

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;

}


复制代码

自己看。没啥复杂的东西,就是记录了一个 memoizedState。

useRef


function mountRef<T>(initialValue: T): {|current: T|} {

  const hook = mountWorkInProgressHook();

  if (enableUseRefAccessWarning) {

    // 原谅我又偷懒,省去了部分源码。

  } else {

    const ref = {current: initialValue};

    hook.memoizedState = ref;

    return ref;

  }

}

复制代码

其实,更简单。只不过现在的React版本加了一个使用UseRef的访问警告。 有兴趣的可以去查查……

updateWorkInProgressHook

噢,既然有mount,自然就有update。 React 使用两套API来分别关心初始化和更新。但如果你看源码的话,会发现updateXXX代码都很简单得令人发指……

不信给你瞅瞅源码。


function updateState<S>(

  initialState: (() => S) | S,

): [S, Dispatch<BasicStateAction<S>>] {

  return updateReducer(basicStateReducer, (initialState: any));

}

function updateRefresh() {

  const hook = updateWorkInProgressHook();

  return hook.memoizedState;

}

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) {

    // Assume these are defined. If they're not, areHookInputsEqual will warn.

    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;

}

……
复制代码

废话不多说,你会发现updateXX就做了大概两件事情。 

  1. 调用 updateWorkInProgressHook 
  2. 更新 hook.memoizedState 

那我们还是来解析一下updateWorkInProgressHook源码


function updateWorkInProgressHook(): Hook {

  // This function is used both for updates and for re-renders triggered by a

  // render phase update. It assumes there is either a current hook we can

  // clone, or a work-in-progress hook from a previous render pass that we can

  // use as a base. When we reach the end of the base list, we must switch to

  // the dispatcher used for mounts.

  let nextCurrentHook: null | Hook;

  if (currentHook === null) { // &1 判断是否是第一个*hooks*

    const current = currentlyRenderingFiber.alternate;

    if (current !== null) {

      nextCurrentHook = current.memoizedState;

    } else {

      nextCurrentHook = null;

    }

  } else {

  

    nextCurrentHook = currentHook.next; // &2 否,那么指向下一个 *hooks*

  }


  let nextWorkInProgressHook: null | Hook;

  if (workInProgressHook === null) { // &3 判断是否第一次访问*hooks*

    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;

  } else {

  

    nextWorkInProgressHook = workInProgressHook.next; // &4 否,则指向……

  }


  if (nextWorkInProgressHook !== null) {

    // There's already a work-in-progress. Reuse it.

    // &5 说明目前已经有hook正在执行,那就重复执行它!

    workInProgressHook = nextWorkInProgressHook;

    nextWorkInProgressHook = workInProgressHook.next;


    currentHook = nextCurrentHook;

  } else {

    // Clone from the current hook.

    invariant(

      nextCurrentHook !== null,

      'Rendered more hooks than during the previous render.',

    );

    currentHook = nextCurrentHook;


    const newHook: Hook = { // &6 创建一个新的hook对象

      memoizedState: currentHook.memoizedState,


      baseState: currentHook.baseState,

      baseQueue: currentHook.baseQueue,

      queue: currentHook.queue,

      next: null,

    };

    if (workInProgressHook === null) {

      // &7 This is the first hook in the list.

      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;

    } else {

      // &8 Append to the end of the list.

      workInProgressHook = workInProgressHook.next = newHook;

    }

  }

  return workInProgressHook;

}
复制代码

小结一下

ps: 贴出来的源码我加的注释可以仔细看下,有助于理解。

其实源码也很简单对伐? 弱弱的说一下,这块可能对大家面试有帮助……

文章分类
前端