react-hooks 源码简读(hooks初始化)

394 阅读7分钟

初始化阶段,在一个函数组件第一次渲染执行上下文过程中,每个react-hooks执行,都会产生一个hook对象,并形成链表结构,绑定在workInProgress的memoizedState属性上,然后react-hooks上的状态,绑定在当前hooks对象的memoizedState属性上。对effect副作用钩子,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树构建完成,在执行每个 effect 副作用钩子。

1.mountWorkInProgressHook

当我们看 HooksDispatcherOnMount 中 每个hook 调用的函数里,都会先调用mountWorkInProgressHook 函数,下面我们来看下这个函数主要做了什么。

react-reconciler/src/ReactFiberHooks.js

function mountWorkInProgressHook(): Hook {
 // 创建hooks对象
  const hook: Hook = {
    memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
    baseState: null, // useState和 useReducer中,一次更新中,产生的最新 state值。
    baseQueue: null, //  useState和useReducer中 保存最新的更新队列。
    queue: null, // 保存待更新队列 pendingQueue ,更新函数 dispatch 等信息。
    next: null, // 指向下一个 hooks对象。
  };
  // 函数组件中第一个hooks
  if (workInProgressHook === null) {
    // hooks链表中的第一个
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 添加到 链表的最后
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

每次执行一个hooks函数,都产生一个hook对象,里面保存了当前hooks信息,然后将每个hook 对象以链表形式串联起来,并赋值给workInProgress的memoizedState。所以, hooks 通过链表顺序来证明唯一性。

2. mountState(初始化useState) and mountReducer(初始化useReducer)

当我们函数组件初始化,执行useState 或useReducer hooks的时候,会执行mountState 或 mountReducer函数。

// useState
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    // 如果 useState 第一个参数为函数,执行函数得到state
    initialState = initialState();
  }
  // 将初始化的state赋值给hook对象的memoizedState 和baseState
  hook.memoizedState = hook.baseState = initialState;
  // 创建一个queue对象,里面保存了负责更新的信息
  const queue = (hook.queue = {
    pending: null, // 待更新的
    interleaved: null,
    lanes: NoLanes,
    dispatch: null, // 更新函数
    lastRenderedReducer: basicStateReducer, // 用于得到最新的state
    lastRenderedState: (initialState: any), // 最后一次得到的 state
  });
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
// useReducer function mountReducer<S, I, A>( 
    reducer: (S, A) => S, 
    initialArg: I, 
    init?: I => S, 
): [S, Dispatch<A>] { 
    const hook = mountWorkInProgressHook(); 
    let initialState; 
    if (init !== undefined) { 
        initialState = init(initialArg); 
    } else { 
        initialState = ((initialArg: any): S);
    } 
    hook.memoizedState = hook.baseState = initialState; 
    const queue = (hook.queue = { 
            pending: null, 
            interleaved: null, 
            lanes: NoLanes,
            dispatch: null, 
            lastRenderedReducer: reducer, 
            lastRenderedState: (initialState: any), 
        }
    ); 
    const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind( 
        null, 
        currentlyRenderingFiber,
        queue, 
    ): any
  )); 
  return [hook.memoizedState, dispatch]; 
}

useState和useReducer触发函数更新的方法都是dispatchAction,useState可以看成一个简化版的useReducer。

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {

  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);
  // 1.创建update对象,记录此次更新的信息,并放入待更新的pending队列
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null: any), // 下一个更新对象
  };
  const alternate = fiber.alternate;
  // 2.判断函数组件当前是否在渲染阶段 
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 3.渲染阶段的更新,将他缓存在待更新队列里。等渲染通过后,我们将在workInProgress hook的最顶端重新开始并调用缓存的更新。
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    // 将待更新队列取出
    const pending = queue.pending;
    if (pending === null) {
      // 第一次更新. 创建一个循环列表.
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }
    queue.pending = update; // 将update对象放入待更新队列
  } else {
    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 transfered to the pending queue.
        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)
    ) {
       // 3.当前组件没有处于调和渲染阶段,这个时候queue对象为空,我们可以在进入渲染阶段之前即刻计算下次的state值。
       // 如果这个新值和现在的state值相等(浅比较),我们也许能够完全退出。(证实useState, 2次值相等的时候,组件不渲染)
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
          const currentState: S = (queue.lastRenderedState: any); // 上一次的state
          const eagerState = lastRenderedReducer(currentState, action); // 获取的新的state
          // 用reducer计算,并缓存这个即刻计算的state值到更新对象上。如果在我们进入渲染阶段的时候,
          // reducer并没有变化,这个即刻计算的state值将不会再被用来调用reducer。
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) { 
          // 如果2次的的state相同,我们可以完全退出,避免react 重新渲染。
          // 如果组件因为其他原因导致重新渲染,在reducer改变时我们也会重置这个update对象。
            return;
          }
        }
      }
    }

    // 如果2次state不相等,则调度scheduleUpdateOnFiber(react渲染更新的主要函数) 渲染当前fiber
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (isTransitionLane(lane) && root !== null) {
      let queueLanes = queue.lanes;
      // If any entangled lanes are no longer pending on the root, then they
      // must have finished. We can remove them from the shared queue, which
      // represents a superset of the actually pending lanes. In some cases we
      // may entangle more than we need to, but that's OK. In fact it's worse if
      // we *don't* entangle when we should.
      queueLanes = intersectLanes(queueLanes, root.pendingLanes);
      // Entangle the new transition lane with the other transition lanes.
      const newQueueLanes = mergeLanes(queueLanes, lane);
      queue.lanes = newQueueLanes;
      // Even if queue.lanes already include lane, we don't know for certain if
      // the lane finished since the last time we entangled it. So we need to
      // entangle it again, just to be sure.
      markRootEntangled(root, newQueueLanes);
    }
  }
  if (enableSchedulingProfiler) {
    markStateUpdateScheduled(fiber, lane);
  }
}
              

3.mountEffect(初始化useEffect) and mountLayoutEffect(初始化useLayoutEffect)

当我们函数组件初始化,执行useEffect 或useLayoutEffect hooks的时候,会执行mountEffect 或mountLayoutEffect函数。

// useEffect
function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
    return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
}

// useLayoutEffect
function mountLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  let fiberFlags: Flags = UpdateEffect;
  if (enableSuspenseLayoutEffectSemantics) {
    fiberFlags |= LayoutStaticEffect;
  }
  
  return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}

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 pushEffect(tag, create, destroy, deps) {
    // 创建effect 对象
  const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: (null: any),
  };
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  // 判断组件是否是第一渲染
  if (componentUpdateQueue === null) {
    // 第一次渲染创建componentUpdateQueue, 然后放入workInProgress 的updateQueue中。
    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 {
      // 将effect 对象插入lastEffect 循环链表中
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}
              

拓展:effectList
effect list 可以理解为是一个存储 effectTag 副作用列表容器。它是由 fiber 节点和指针 nextEffect 构成的单链表结构,这其中还包括第一个节点 firstEffect ,和最后一个节点 lastEffect。

React 采用深度优先搜索算法,在 render 阶段遍历 fiber 树时,把每一个有副作用的 fiber 筛选出来,最后构建生成一个只带副作用的 effect list 链表。
在 commit 阶段,React 拿到 effect list 数据后,通过遍历 effect list,并根据每一个 effect 节点的 effectTag 类型,执行每个effect,从而对相应的 DOM 树执行更改。

4. mountImperativeHandle(初始化useImperativeHandle)

当我们函数组件初始化,执行useImperativeHandle时候,会执行mountImperativeHandle函数。

function mountImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {

  // TODO: If deps are provided, should we skip comparing the ref itself?
  const effectDeps =
    deps !== null && deps !== undefined ? deps.concat([ref]) : null;
  let fiberFlags: Flags = UpdateEffect;
  if (enableSuspenseLayoutEffectSemantics) {
    fiberFlags |= LayoutStaticEffect;
  }
  
  return mountEffectImpl(
    fiberFlags,
    HookLayout,
    imperativeHandleEffect.bind(null, create, ref),
    effectDeps,
  );
}

function imperativeHandleEffect<T>(
  create: () => T,
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
) {
  if (typeof ref === 'function') {
    const refCallback = ref;
    const inst = create();
    refCallback(inst);
    return () => {
      refCallback(null);
    };
  } else if (ref !== null && ref !== undefined) {
    const refObject = ref;
    // 通过create() 返回的值定制ref.current 值
    const inst = create();
    refObject.current = inst;
    return () => {
      refObject.current = null;
    };
  }
}
              

5.mountMemo(初始化useMemo) and mountCallback(初始化useCallback)

当我们函数组件初始化,执行useMemo 或useCallback hooks的时候,会执行mountMemo 或mountCallback函数。从下面代码可以看出2个hooks 的区别就在于,hook对象的memoizedState 缓存的值不一样。useMemo 存的是计算后的值和依赖,而useCallback 存的是计算本身和依赖。

// 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;
}

// useCallback
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
              

6.mountRef(初始化useRef)

当我们函数组件初始化,执行useRef hooks的时候,会执行mountRef函数,他会在hooks 对象的memoizedState 里存入一个包含current 的对象。

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

下一篇,我们将学习下hooks的更新。

最后推荐下我的个人网站- 【良月清秋的前端日志】(animasling.github.io/front-end-b…) ,希望我的文章对你有帮助。