美文网首页
useReducer - React源码解析(七)

useReducer - React源码解析(七)

作者: 请叫我Pro大叔 | 来源:发表于2020-06-29 19:36 被阅读0次

    本文将讲解useReducer钩子的实现。useReducer可以看做一个轻量级的Redux版本,也是useState钩子的基础。

    Mount阶段

    在这个阶段:

    1. useReducer首先调用mountWorkInProgressHook创建一个Hook对象
    2. 然后,计算初始状态值initialState,该值可以是由第二个参数(当不传第三个参数时)传入,也可由第三个参数(只能是函数)和第二个参数计算所得。
    3. 创建queue以及dispatch函数
    4. 返回初始值initialState以及dispatch函数
    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,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      return [hook.memoizedState, dispatch];
    }
    

    Update阶段

    这个阶段的实现相对比较复杂。总体上来说,分成三个阶段:

    1. 获取Mount阶段的Hook实例
    2. 处理queue
    3. 返回新的值
    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = updateWorkInProgressHook();
      const queue = hook.queue;
      invariant(
        queue !== null,
        'Should have a queue. This is likely a bug in React. Please file an issue.',
      );
    
      queue.lastRenderedReducer = reducer;
    
      const current: Hook = (currentHook: any);
    
      // The last rebase update that is NOT part of the base state.
      let baseQueue = current.baseQueue;
    
      // The last pending update that hasn't been processed yet.
      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.
        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;
        }
        if (__DEV__) {
          if (current.baseQueue !== baseQueue) {
            // Internal invariant that should never happen, but feasibly could in
            // the future if we implement resuming, or some form of that.
            console.error(
              'Internal error: Expected work-in-progress queue to be a clone. ' +
                'This is a bug in React.',
            );
          }
        }
        current.baseQueue = baseQueue = pendingQueue;
        queue.pending = null;
      }
    
      if (baseQueue !== null) {
        // We have a queue to process.
        const first = baseQueue.next;
        let newState = current.baseState;
    
        let newBaseState = null;
        let newBaseQueueFirst = null;
        let newBaseQueueLast = null;
        let update = first;
        do {
          const suspenseConfig = update.suspenseConfig;
          const updateLane = update.lane;
          const updateEventTime = update.eventTime;
          if (!isSubsetOfLanes(renderLanes, updateLane)) {
            // Priority is insufficient. Skip this update. If this is the first
            // skipped update, the previous update/state is the new base
            // update/state.
            const clone: Update<S, A> = {
              eventTime: updateEventTime,
              lane: updateLane,
              suspenseConfig: suspenseConfig,
              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;
            }
            // Update the remaining priority in the queue.
            // TODO: Don't need to accumulate this. Instead, we can remove
            // renderLanes from the original lanes.
            currentlyRenderingFiber.lanes = mergeLanes(
              currentlyRenderingFiber.lanes,
              updateLane,
            );
            markSkippedUpdateLanes(updateLane);
          } else {
            // This update does have sufficient priority.
    
            if (newBaseQueueLast !== null) {
              const clone: Update<S, A> = {
                eventTime: updateEventTime,
                // 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,
                suspenseConfig: update.suspenseConfig,
                action: update.action,
                eagerReducer: update.eagerReducer,
                eagerState: update.eagerState,
                next: (null: any),
              };
              newBaseQueueLast = newBaseQueueLast.next = clone;
            }
    
            // Mark the event time of this update as relevant to this render pass.
            // TODO: This should ideally use the true event time of this update rather than
            // its priority which is a derived and not reverseable value.
            // TODO: We should skip this update if it was already committed but currently
            // we have no way of detecting the difference between a committed and suspended
            // update here.
            markRenderEventTimeAndConfig(updateEventTime, suspenseConfig);
    
            // Process this update.
            if (update.eagerReducer === reducer) {
              // If this update was processed eagerly, and its reducer matches the
              // current reducer, we can use the eagerly computed state.
              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);
        }
    
        // Mark that the fiber performed work, but only if the new state is
        // different from the current state.
        if (!is(newState, hook.memoizedState)) {
          markWorkInProgressReceivedUpdate();
        }
    
        hook.memoizedState = newState;
        hook.baseState = newBaseState;
        hook.baseQueue = newBaseQueueLast;
    
        queue.lastRenderedState = newState;
      }
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
    

    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().',
          );
        }
      }
    
      const eventTime = requestEventTime();
      const suspenseConfig = requestCurrentSuspenseConfig();
      const lane = requestUpdateLane(fiber, suspenseConfig);
    
      const update: Update<S, A> = {
        eventTime,
        lane,
        suspenseConfig,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
      };
    
      // Append the update to the end of the list.
      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;
    
      const alternate = fiber.alternate;
      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;
      } else {
        if (
          fiber.lanes === NoLanes &&
          (alternate === null || alternate.lanes === NoLanes)
        ) {
          // The queue is currently empty, which means we can eagerly compute the
          // next state before entering the render phase. If the new state is the
          // same as the current state, we may be able to bail out entirely.
          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);
              // Stash the eagerly computed state, and the reducer used to compute
              // it, on the update object. If the reducer hasn't changed by the
              // time we enter the render phase, then the eager state can be used
              // without calling the reducer again.
              update.eagerReducer = lastRenderedReducer;
              update.eagerState = eagerState;
              if (is(eagerState, currentState)) {
                // Fast path. We can bail out without scheduling React to re-render.
                // It's still possible that we'll need to rebase this update later,
                // if the component re-renders for a different reason and by that
                // time the reducer has changed.
                return;
              }
            } catch (error) {
              // Suppress the error. It will throw again in the render phase.
            } finally {
              if (__DEV__) {
                ReactCurrentDispatcher.current = prevDispatcher;
              }
            }
          }
        }
        if (__DEV__) {
          // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
          if ('undefined' !== typeof jest) {
            warnIfNotScopedWithMatchingAct(fiber);
            warnIfNotCurrentlyActingUpdatesInDev(fiber);
          }
        }
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    

    相关文章

      网友评论

          本文标题:useReducer - React源码解析(七)

          本文链接:https://www.haomeiwen.com/subject/abyafktx.html