关于react.js:react源码解析13hooks源码

29次阅读

共计 11406 个字符,预计需要花费 29 分钟才能阅读完成。

react 源码解析 13.hooks 源码

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

21.demo

hook 调用入口

​ 在 hook 源码中 hook 存在于 Dispatcher 中,Dispatcher 就是一个对象,不同 hook 调用的函数不一样,全局变量 ReactCurrentDispatcher.current 会依据是 mount 还是 update 赋值为 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate

ReactCurrentDispatcher.current = 
  current === null || current.memoizedState === null//mount or update
  ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;  
const HooksDispatcherOnMount: Dispatcher = {//mount 时
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  //...
};

const HooksDispatcherOnUpdate: Dispatcher = {//update 时
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  //...
};

hook 数据结构

​ 在 FunctionComponent 中,多个 hook 会造成 hook 链表,保留在 Fiber 的 memoizedState 的上,而须要更新的 Update 保留在 hook.queue.pending 中

const hook: Hook = {
  memoizedState: null,// 对于不同 hook,有不同的值
  baseState: null,// 初始 state
  baseQueue: null,// 初始 queue 队列
  queue: null,// 须要更新的 update
  next: null,// 下一个 hook
};

上面来看下 memoizedState 对应的值

  • useState:例如const [state, updateState] = useState(initialState)memoizedState 等于state 的值
  • useReducer:例如const [state, dispatch] = useReducer(reducer, {});memoizedState 等于state 的值
  • useEffect:在 mountEffect 时会调用 pushEffect 创立 effect 链表,memoizedState就等于 effect 链表,effect 链表也会挂载到 fiber.updateQueue 上,每个 effect 上存在 useEffect 的第一个参数回调和第二个参数依赖数组,例如,useEffect(callback, [dep]),effect 就是{create:callback, dep:dep,…}
  • useRef:例如 useRef(0),memoizedState 就等于{current: 0}
  • useMemo:例如 useMemo(callback, [dep])memoizedState 等于[callback(), dep]
  • useCallback:例如 useCallback(callback, [dep])memoizedState 等于 [callback, dep]useCallback 保留 callback 函数,useMemo保留 callback 的执行后果

useState&useReducer

之所以把 useState 和 useReducer 放在一起,是因为在源码中 useState 就是有默认 reducer 参数的 useReducer。

  • useState&useReducer 申明

    ​ resolveDispatcher 函数会获取以后的 Dispatcher

    function useState(initialState) {var dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
    function useReducer(reducer, initialArg, init) {var dispatcher = resolveDispatcher();
      return dispatcher.useReducer(reducer, initialArg, init);
    }
    
  • mount 阶段

    ​ mount 阶段 useState 调用 mountState,useReducer 调用 mountReducer,惟一区别就是它们创立的 queue 中 lastRenderedReducer 不一样,mount 有初始值 basicStateReducer,所以说 useState 就是有默认 reducer 参数的 useReducer。

    function mountState<S>(//
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {const hook = mountWorkInProgressHook();// 创立以后 hook
      if (typeof initialState === 'function') {initialState = initialState();
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState 赋值
      const queue = (hook.queue = {// 赋值 hook.queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,// 和 mountReducer 的区别
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<// 创立 dispatch 函数
        BasicStateAction<S>,
      > = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      return [hook.memoizedState, dispatch];// 返回 memoizedState 和 dispatch
    }
    
    function mountReducer<S, I, A>(reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {const hook = mountWorkInProgressHook();// 创立以后 hook
      let initialState;
      if (init !== undefined) {initialState = init(initialArg);
      } else {initialState = ((initialArg: any): S);
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState 赋值
      const queue = (hook.queue = {// 创立 queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(// 创立 dispatch 函数
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      return [hook.memoizedState, dispatch];// 返回 memoizedState 和 dispatch
    }
    
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {return typeof action === 'function' ? action(state) : action;
}
  • update 阶段

    update 时会依据 hook 中的 update 计算新的 state

    function updateReducer<S, I, A>(reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {const hook = updateWorkInProgressHook();// 获取 hook
      const queue = hook.queue;
      queue.lastRenderedReducer = reducer;
    
      //... 更新 state 和第 12 章的 state 计算逻辑基本一致
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
    
  • 执行阶段

    useState 执行 setState 后会调用 dispatchAction,dispatchAction 做的事件就是讲 Update 退出 queue.pending 中,而后开始调度

    function dispatchAction(fiber, queue, action) {
    
      var update = {// 创立 update
        eventTime: eventTime,
        lane: lane,
        suspenseConfig: suspenseConfig,
        action: action,
        eagerReducer: null,
        eagerState: null,
        next: null
      }; 
    
      //queue.pending 中退出 update
      
      var alternate = fiber.alternate;
    
      if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {// 如果是 render 阶段执行的更新 didScheduleRenderPhaseUpdate=true}
        didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
      } else {if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
          // 如果 fiber 不存在优先级并且以后 alternate 不存在或者没有优先级,那就不须要更新了
          // 优化的步骤
        }
    
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    

useEffect

  • 申明

    获取并返回 useEffect 函数

export function useEffect(create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}
  • mount 阶段

    ​ 调用 mountEffect,mountEffect 调用 mountEffectImpl,hook.memoizedState 赋值为 effect 链表

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {const hook = mountWorkInProgressHook();// 获取 hook
  const nextDeps = deps === undefined ? null : deps;// 依赖
  currentlyRenderingFiber.flags |= fiberFlags;// 减少 flag
  hook.memoizedState = pushEffect(//memoizedState=effects 环状链表
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}
  • update 阶段

    ​ 浅比拟依赖,如果依赖性变了 pushEffect 第一个参数传 HookHasEffect | hookFlags,HookHasEffect 示意 useEffect 依赖项扭转了,须要在 commit 阶段从新执行

function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;

  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;//
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      if (areHookInputsEqual(nextDeps, prevDeps)) {// 比拟 deps
        // 即便依赖相等也要将 effect 退出链表,以保障程序统一
        pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;

  hook.memoizedState = pushEffect(
    // 参数传 HookHasEffect | hookFlags,蕴含 hookFlags 的 useEffect 会在 commit 阶段执行这个 effect
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}
  • 执行阶段

    ​ 在第 9 章 commit 阶段的 commitLayoutEffects 函数中会调用 schedulePassiveEffects,将 useEffect 的销毁和回调函数 push 到 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 中,而后在 mutation 之后调用 flushPassiveEffects 顺次执行上次 render 的销毁函数回调和本次 render 的回调函数

const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
for (let i = 0; i < unmountEffects.length; i += 2) {const effect = ((unmountEffects[i]: any): HookEffect);
  const fiber = ((unmountEffects[i + 1]: any): Fiber);
  const destroy = effect.destroy;
  effect.destroy = undefined;

  if (typeof destroy === 'function') {
    try {destroy();// 销毁函数执行
    } catch (error) {captureCommitPhaseError(fiber, error);
    }
  }
}

const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {const effect = ((mountEffects[i]: any): HookEffect);
  const fiber = ((mountEffects[i + 1]: any): Fiber);
  
  try {
    const create = effect.create;// 本次 render 的创立函数
   effect.destroy = create();} catch (error) {captureCommitPhaseError(fiber, error);
  }
}

useRef

​ sring 类型的 ref 曾经不在举荐应用 (源码中 string 会生成 refs,产生在 coerceRef 函数中),ForwardRef 只是把 ref 通过传参传下去,createRef 也是{current: any 这种构造,所以咱们只探讨 function 或者{current: any} 的 useRef

//createRef 返回{current: any}
export function createRef(): RefObject {
  const refObject = {current: null,};
  return refObject;
}
  • 申明阶段

    ​ 和其余 hook 一样

export function useRef<T>(initialValue: T): {|current: T|} {const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}
  • mount 阶段

    ​ mount 时会调用 mountRef,创立 hook 和 ref 对象。

function mountRef<T>(initialValue: T): {|current: T|} {const hook = mountWorkInProgressHook();// 获取 useRef
  const ref = {current: initialValue};//ref 初始化
  hook.memoizedState = ref;
  return ref;
}

​ render 阶段:将带有 ref 属性的 Fiber 标记上 Ref Tag,这一步产生在 beginWork 和 completeWork 函数中的 markRef

export const Ref = /*                          */ 0b0000000010000000;
//beginWork 中
function markRef(current: Fiber | null, workInProgress: Fiber) {
  const ref = workInProgress.ref;
  if ((current === null && ref !== null) ||
    (current !== null && current.ref !== ref)
  ) {workInProgress.effectTag |= Ref;}
}
//completeWork 中
function markRef(workInProgress: Fiber) {workInProgress.effectTag |= Ref;}

​ commit 阶段:

​ 会在 commitMutationEffects 函数中判断 ref 是否扭转,如果扭转了会先执行 commitDetachRef 先删除之前的 ref,而后在 commitLayoutEffect 中会执行 commitAttachRef 赋值 ref。

function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;
    // ...
    
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {commitDetachRef(current);// 移除 ref
      }
    }
  }
function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {if (typeof currentRef === 'function') {currentRef(null);// 类型是 function,则调用
    } else {currentRef.current = null;// 否则赋值{current: null}
    }
  }
}
function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;// 获取 ref 的实例
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostComponent:
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }

    if (typeof ref === 'function') {//ref 赋值
      ref(instanceToUse);
    } else {ref.current = instanceToUse;}
  }
}
  • update 阶段

    ​ update 时调用 updateRef 获取获取以后 useRef,而后返回 hook 链表

function updateRef<T>(initialValue: T): {|current: T|} {const hook = updateWorkInProgressHook();// 获取以后 useRef
  return hook.memoizedState;// 返回 hook 链表
}

useMemo&useCallback

  • 申明阶段

    和其余 hook 一样

  • mount 阶段

    mount 阶段 useMemo 和 useCallback 惟一区别是在 memoizedState 中存贮 callback 还是 callback 计算出来的函数

function mountMemo<T>(nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {const hook = mountWorkInProgressHook();// 创立 hook
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();// 计算 value
  hook.memoizedState = [nextValue, nextDeps];// 把 value 和依赖保留在 memoizedState 中
  return nextValue;
}

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {const hook = mountWorkInProgressHook();// 创立 hook
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];// 把 callback 和依赖保留在 memoizedState 中
  return callback;
}
  • update 阶段

    update 时也一样,惟一区别就是间接用回调函数还是执行回调后返回的 value 作为 [?, nextDeps] 赋值给 memoizedState

function updateMemo<T>(nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {const hook = updateWorkInProgressHook();// 获取 hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {if (nextDeps !== null) {const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {// 浅比拟依赖
        return prevState[0];// 没变 返回之前的状态
      }
    }
  }
  const nextValue = nextCreate();// 有变动从新调用 callback
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {const hook = updateWorkInProgressHook();// 获取 hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {if (nextDeps !== null) {const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {// 浅比拟依赖
        return prevState[0];// 没变 返回之前的状态
      }
    }
  }

  hook.memoizedState = [callback, nextDeps];// 变了从新将 [callback, nextDeps] 赋值给 memoizedState
  return callback;
}

useLayoutEffect

useLayoutEffect 和 useEffect 一样,只是调用的机会不同,它是在 commit 阶段的 commitLayout 函数中同步执行

forwardRef

forwardRef 也非常简单,就是传递 ref 属性

export function forwardRef<Props, ElementType: React$ElementType>(render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  
  const elementType = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
  
  return elementType;
}
//ForwardRef 第二个参数是 ref 对象
let children = Component(props, secondArg);

正文完
 0