React源码解析 (八) —— useState

398 阅读5分钟

useState

useState是React的众多hooks之一,在react包中的ReactHooks文件中定义:

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

可以看到函数主要是调用了resolveDispatcher()返回的useState方法,实际上resolveDispatcher()返回的是ReactCurrentDispatcher.current。 来看一下ReactCurrentDispatcher.current的值是什么?

对函数的赋值发生在renderWithHooks函数中我们省略其他处理得到:

    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;

当初始化hook的过程中会先调用HooksDispatcherOnMount,后续更新过程中则会调用HooksDispatcherOnUpdate

初始化State :mountState

image.png 通过调用堆栈结合代码可以看出mount阶段 HooksDispatcherOnMount中的useState指向的是mountState函数

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

函数中进行如下操作:

1. 生成hook对象:

hook对象通过mountWorkInProgressHook函数生成:

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

函数中首先初始化了一个Hook实例,然后对当前Fiber的workInProgressHook进行判断,如果是null,则将生成的hook对象挂载到当前Fiber的memoizedState上,用于存储Fiber节点对应的useState hook的初始值,并给workInProgressHook赋值。如果已经初始化过,则将hook赋值给workInProgressHookworkInProgressHook.next形成一个链表结构。

2.初始化state的值

  hook.memoizedState = hook.baseState = initialState;

3.生成一个udateQueue

  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };

UpdateQueue是React中用于管理和处理状态更新的数据结构 其中:

export type UpdateQueue<S, A> = {
  pending: Update<S, A> | null,
  lanes: Lanes,
  dispatch: (A => mixed) | null,
  lastRenderedReducer: ((S, A) => S) | null,
  lastRenderedState: S | null,
};
  1. pending: 指向当前待处理的更新对象
  2. lanes: lane优先级集合
  3. dispatch分发函数,它允许从外部向这个更新队列调度新的更新
  4. lastRenderedReducer最后一次渲染时使用的reducer函数,用于根据当前状态和动作计算新的状态
  5. lastRenderedState最后一次渲染时的状态

4.初始化dispatch

dispatch实际上上是使用了bind的dispatchSetState函数,该函数会在更新state的方法被调用时执行

5.返回

函数最后会返回hook中储存的当前状态和dispatch方法,就是我们在使用useState时的两个返回值

const [xxx, setXXX] = useState()

修改state : dispatchSetState

当需要改变state的值时dispatchSetState会被调用 来看一下函数做了什么事情:

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

  const lane = requestUpdateLane(fiber);

  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };

  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  markUpdateInDevTools(fiber, lane, action);
}

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

  const lane = requestUpdateLane(fiber);

  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };

  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    const alternate = fiber.alternate;
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          update.hasEagerState = true;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
            return;
          }
        } catch (error) {
        } finally {
        }
      }
    }

    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  markUpdateInDevTools(fiber, lane, action);
}
  1. 调用requestUpdateLane,用于获取fiber更新的优先级

  2. 调用isRenderPhaseUpdate用于判断是否是render阶段产生的更新,如果是的话会将update对象加到,updateQueue的pending链表之上

  3. 进行eagerState优化策略处理:
    通过fiber.lanes是不是NoLanes,判断目前fiberNode是否有待处理的更新,如果没有则计算eagerState, 函数内部首先获取了lastRenderedReducer,在setState中其为basicStateReducer,可以看到内部执行了action或者直接赋值

    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      return typeof action === 'function' ? action(state) : action;
    }
    

    在执行了lastRenderedReducer后将得到的值和currentState进行比较(is(eagerState, currentState)),如果值没有变化,则不再需要进入scheduler。

  4. 如果没有命中eagerState优化策略,则调用enqueueConcurrentHookUpdate

    export function enqueueConcurrentHookUpdate<S, A>(
       fiber: Fiber,
       queue: HookQueue<S, A>,
       update: HookUpdate<S, A>,
       lane: Lane,
     ): FiberRoot | null {
       const concurrentQueue: ConcurrentQueue = (queue: any);
       const concurrentUpdate: ConcurrentUpdate = (update: any);
       enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
       return getRootForUpdatedFiber(fiber);
     }
    

    enqueueUpdate中更新被加入到队列之中

消费队列中的update

1.处理更新队列

dispatchSetState的最后,scheduleUpdateOnFiber进行调度更新,在render阶段,函数调用至 prepareFreshStack ,其中会调用finishQueueingConcurrentUpdates函数,对更新队列进行处理

export function finishQueueingConcurrentUpdates(): void {
  const endIndex = concurrentQueuesIndex;
  concurrentQueuesIndex = 0;

  concurrentlyUpdatedLanes = NoLanes;

  let i = 0;
  while (i < endIndex) {
    const fiber: Fiber = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const queue: ConcurrentQueue = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const update: ConcurrentUpdate = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const lane: Lane = concurrentQueues[i];
    concurrentQueues[i++] = null;

    if (queue !== null && update !== null) {
      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 (lane !== NoLane) {
      markUpdateLaneFromFiberToRoot(fiber, update, lane);
    }
  }
}

通过处理会使得pending始终指向最后一个update,最后一个update通过next又能指向第一个update

2. 更新组件过程中会再次调用useState

具体调度渲染过程不再阐述,来看一下调用堆栈:

image.png

可以看到在渲染过程中调用的useState实际上执行的是updateReducer

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  queue.lastRenderedReducer = reducer;

  const current: Hook = (currentHook: any);

  let baseQueue = current.baseQueue;

  const pendingQueue = queue.pending;
  
  if (pendingQueue !== null) {
 
    if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }

    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }

  if (baseQueue !== null) {
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    
    do {
      const updateLane = removeLanes(update.lane, OffscreenLane);
      const isHiddenUpdate = updateLane !== update.lane;

      const shouldSkipUpdate = isHiddenUpdate
        ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
        : !isSubsetOfLanes(renderLanes, updateLane);

      if (shouldSkipUpdate) {
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          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> = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        if (update.hasEagerState) {
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  if (baseQueue === null) {
    queue.lanes = NoLanes;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

函数主要流程:

  1. 调用updateWorkInProgressHook获取了我们在初始化阶段挂载在Fiber上的hook实例。
  2. 判断pendingQueue是否为null, 如果存在并且baseQueue不为null,将baseQueuependingQueue相连,相连后作为新的baseQueue,保存到当前Fiber。
  3. 判断是否是需要跳过的更新,判断更新的优先级中是否有OffscreenLane优先级,并且进行判断检查当前update的lane属不属于render阶段的更新,不是则跳过处理过程,如果属于则尝试从hasEagerState中取值,或者使用action计算新值,对state的值进行更新。
  4. 最后返回新的statedispatch方法。

总结

本文解析了React中useState Hook的实现,包括其在组件挂载和更新时的行为。详细介绍了useState如何创建和管理状态,以及如何通过dispatchSetState处理状态更新。最后,探讨了组件更新时如何消费更新队列,确保状态正确更新。