react-hooks 源码简读(hooks更新)

279 阅读6分钟

对于一次函数组件更新,当再次执行hooks函数的时候,比如 useState(0) ,首先会调用updateWorkInProgressHook函数,从current的hooks中找到与当前workInProgressHook对应的currentHooks,然后复制一份currentHooks给workInProgressHook,从而获取到workInProgressHook,接下来hooks函数执行的时候,就会把最新的状态更新到workInProgressHook,保证hooks状态不丢失。

1.updateWorkInProgressHook

这个函数会在更新,或是在渲染阶段更新触发的重新渲染时调用。

 function updateWorkInProgressHook(): Hook {
  // 假设这儿有一个 current 树的hook 我们可以克隆,或是一个来自之前渲染的workInProgress 的 hook 我们可以作为基本来用。
  // 当我们到达这个基本列表的最后,我们必须切换到mount的dispatcher。
  let nextCurrentHook: null | Hook;
  if (currentHook === null) { // 第一个hooks
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;  
    } else {
      nextCurrentHook = null;
    }
  } else {
    // 不是第一个hooks,那么指向下一个 hooks
    nextCurrentHook = currentHook.next;
  }
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) { // 第一次执行hooks函数
    // 这里应该注意一下,当函数组件更新也是调用 renderWithHooks ,memoizedState属性是置空的
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState; // 将current 树的hooks 链表复制出来(旧的hooks)
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }
  if (nextWorkInProgressHook !== null) {
    // 这个情况说明 renderWithHooks 执行 过程发生多次函数组件的执行 ,我们暂时先不考虑 */

    // There's already a work-in-progress. Reuse it.
    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 = { // 新建一个hook 对象
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };
    if (workInProgressHook === null) {
      // 列表中的第一个hooks
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // 添加到列表的最后, 更新hooks列表
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}
              

2. updateState(useState更新) and updateReducer(useReducer更新)

当我们函数组件更新,执行useState 或useReducer hooks的时候,会执行updateState 或updateReducer函数, 从下面代码可以看到,updateState 底层其实就是调用的 updateReducer。

// useState
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

// useReducer
function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  
  // 1.获取之前的更新队列
  const queue = hook.queue; 
  queue.lastRenderedReducer = reducer;
  // 更新当前hooks的baseQueue
  const current: Hook = (currentHook: any);
  // The last rebase update that is NOT part of the base state.
  let baseQueue = current.baseQueue;

  // 2.将之前的更新队列合并到baseQueue
  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    // We have new updates that haven't been processed yet.
    // We'll add them to the base queue.
    // 如果有新的更新,将他们与之前的更新队列合并,并赋值给baseQueue
    if (baseQueue !== null) {
      // Merge the pending queue and the base queue.
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  
  // 3.如果有更新,则开始循环更新
  if (baseQueue !== null) {
    // We have a queue to process.
    const first = baseQueue.next;
    let newState = current.baseState; // 旧state 值
    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    // 循环baseQueue, 获取最新的state值
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // 如果优先权低,则跳过这个更新。如果这是第一个跳过的更新,则之前的update/state 则是新 的baseUpdate /baseState
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          eagerReducer: update.eagerReducer,
          eagerState: update.eagerState,
          next: (null: any),
        };
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 更新剩下队列的优先级
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        // 更新有足够的优先级
        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            // This update is going to be committed so we never want uncommit
            // it. Using NoLane works because 0 is a subset of all bitmasks, so
            // this will never be skipped by the check above.
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 执行更新
        if (update.eagerReducer === reducer) {
          // 如果这个update被立即执行,并且他的reducer 和 当前更新对象的 reducer 匹配, 我们可以用立即计算的state 值
          newState = ((update.eagerState: any): S);
        } else {
          // 不然用reducer 获取新的值
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      // 指向下一个更新,直到执行完所有的更新,获取到最新的newState 值
      update = update.next;
    } while (update !== null && update !== first);
    
    // 更新baseState 到最新值
    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }
    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }
    // 4.更新当前hook对象值
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }
  // Interleaved updates are stored on a separate queue. We aren't going to
  // process them during this render, but we do need to track which lanes
  // are remaining.
  const lastInterleaved = queue.interleaved;
  if (lastInterleaved !== null) {
    let interleaved = lastInterleaved;
    do {
      const interleavedLane = interleaved.lane;
      currentlyRenderingFiber.lanes = mergeLanes(
        currentlyRenderingFiber.lanes,
        interleavedLane,
      );
      markSkippedUpdateLanes(interleavedLane);
      interleaved = ((interleaved: any).next: Update<S, A>);
    } while (interleaved !== lastInterleaved);
  } else if (baseQueue === null) {
    // `queue.lanes` is used for entangling transitions. We can set it back to
    // zero once the queue is empty.
    queue.lanes = NoLanes;
  }
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}
              

3.updateEffect(useEffect更新) and updateLayoutEffect(useLayoutEffect更新)

当我们函数组件更新,执行useEffect 或useLayoutEffect hooks的时候,会执行updateEffect 或updateLayoutEffect函数。

 // useEffectfunction
updateEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}

// useLayoutEffect
function updateLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}

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; // 之前hook里effect 对象
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 之前的依赖和现在的依赖相等,则这次更新不需要执行,直接调用pushEffect
      if (areHookInputsEqual(nextDeps, prevDeps)) {
          // 获取新的effect对象,并赋值给hook.memoizedState
          hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps); 
        return;
      }
    }
  }
  currentlyRenderingFiber.flags |= fiberFlags;
  // effect的标签,hookFlags,如果不相等,那么更新effect ,并且赋值给hook.memoizedState,
  // 这里标签是 HookHasEffect | hookFlags,然后在commit阶段,react会通过标签来判断,是否执行当前的 effect 函数。
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}
              

4. updateImperativeHandle(useImperativeHandle更新)

当我们函数组件更新,执行useImperativeHandl时候,会执行updateImperativeHandle函数。

function updateImperativeHandle<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;
  return updateEffectImpl(
    UpdateEffect,
    HookLayout,
    imperativeHandleEffect.bind(null, create, ref),
    effectDeps,
  );
}

5.updateMemo(useMemo更新) and updateCallback(useCallback更新)

当我们函数组件更新,执行useMemo 或useCallback hooks的时候,会执行updateMemo 或updateCallback函数。

// useMemo
function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState; // 获取之前的useMemo hooks 信息
  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)) { // 2次依赖相等,直接返回之前缓存的值
        return prevState[0]; 
      }
    }
  }
  // 之前hook对象memoizedState没有值,执行第一个参数,并将所得值和依赖赋值给 hook.memoizedState
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

// useCallback
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 如果2次依赖相等,则返回之前缓存的callback
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
              

nextCreate()执行,如果里面引用了useState等信息,变量会被引用,无法被垃圾回收机制回收,就是闭包原理,那么访问的属性有可能不是最新的值,所以需要把引用的值,添加到依赖项 dep 数组中。每一次dep改变,重新执行,就不会出现问题了。

6. updateRef(useRef更新)

无论函数组件怎么执行,执行多少次,hook.memoizedState内存中都指向了一个对象,所以解释了useEffect,useMemo 中,为什么useRef不需要依赖注入,就能访问到最新的改变值。

function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

如果你看到了这里,恭喜你,今天又进步了一点点呢,给自己一个大大的赞吧!

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