React 深入学习:React 更新队列

1,896 阅读10分钟

path:packages/react-reconciler/src/ReactUpdateQueue.js

更新

export type Update<State> = {
  expirationTime: ExpirationTime, // 到期时间

  tag: 0 | 1 | 2 | 3, // 更新类型
  payload: any, // 负载
  callback: (() => mixed) | null, // 回调函数

  next: Update<State> | null, // 下一个更新
  nextEffect: Update<State> | null, // 下一个效果
};

React 的状态更新分为四种情况,他们分别对应 Update 的 tag 属性的四个值:

  • UpdateState
  • ReplaceState
  • ForceUpdate
  • CaptureUpdate
export const UpdateState = 0; // 更新状态
export const ReplaceState = 1; // 替换状态
export const ForceUpdate = 2; // 强制更新
export const CaptureUpdate = 3; // 捕获更新

创建更新

/**
 * 创建更新
 * @param expirationTime
 * @returns {{next: null, payload: null, expirationTime: ExpirationTime, callback: null, tag: number, nextEffect: null}}
 */
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    expirationTime: expirationTime,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
    nextEffect: null,
  };
}

调用此方法创建的更新默认为是局部更新,需要合并前后状态。

更新队列

export type UpdateQueue<State> = {
  baseState: State,

  firstUpdate: Update<State> | null,
  lastUpdate: Update<State> | null,

  firstCapturedUpdate: Update<State> | null,
  lastCapturedUpdate: Update<State> | null,

  firstEffect: Update<State> | null,
  lastEffect: Update<State> | null,

  firstCapturedEffect: Update<State> | null,
  lastCapturedEffect: Update<State> | null,
};

创建更新队列

/**
 * 创建更新队列
 * @param baseState
 * @returns {UpdateQueue<State>}
 */
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    baseState,
    firstUpdate: null,
    lastUpdate: null,
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

数据结构

从上面的代码中可以看到,更新队列是一个单向链表:

appendUpdateToQueue

追加更新到链表尾部

/**
 * 添加更新到队列中
 * @param queue
 * @param update
 */
function appendUpdateToQueue<State>(
  queue: UpdateQueue<State>,
  update: Update<State>,
) {
  // 将更新追加到列表的末尾。
  if (queue.lastUpdate === null) {
    // 队列是空的
    queue.firstUpdate = queue.lastUpdate = update;
  } else {
    queue.lastUpdate.next = update;
    queue.lastUpdate = update;
  }
}

state 更新

每次更新的时候需要根据不同的更新类型来获取下一次的 state:

  • UpdateState 需要合并前一次的状态和本次的状态
  • ReplaceState 直接使用下一次的状态
  • ForceUpdate 使用前一次的状态
  • CaptureUpdate
/**
 * 从跟新获取状态
 * @param workInProgress
 * @param queue
 * @param update
 * @param prevState
 * @param nextProps
 * @param instance
 * @returns {State|*}
 */
function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  switch (update.tag) {
    case ReplaceState: {
      const payload = update.payload;
      if (typeof payload === 'function') {
        // 更新器函数
        const nextState = payload.call(instance, prevState, nextProps);
        return nextState;
      }
      // 状态对象
      return payload;
    }
    case CaptureUpdate: {
      workInProgress.effectTag =
        (workInProgress.effectTag & ~ShouldCapture) | DidCapture;
    }
    // Intentional fallthrough
    case UpdateState: {
      const payload = update.payload;
      let partialState;
      if (typeof payload === 'function') {
        // Updater function
        partialState = payload.call(instance, prevState, nextProps);
      } else {
        // 部分状态对象
        partialState = payload;
      }
      if (partialState === null || partialState === undefined) {
        // Null 和 undefined 被视为 no-ops。
        return prevState;
      }
      // 合并部分状态和前一个状态。
      return Object.assign({}, prevState, partialState);
    }
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}

从上面的代码可以看到,更新 state 时可以接收一个更新器函数,这个更新器函数被绑定到当前的实例上运行,也就是在 React 文档 中写到的,setState 可以接收一个函数作为参数:

setState((prevState, nextProps) => {
    // do something
})
  • prevState 参数是上一次调用 setState 之后的状态,而不是已经更新到 dom 中的状态,因为状态更新是异步的,为了避免不必要的重新渲染来提升性能。
  • nextProps 参数是下一次的 props 对象

处理更新

/**
 * 
 * @param workInProgress
 * @param queue
 * @param props
 * @param instance
 * @param renderExpirationTime
 */
export function processUpdateQueue<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  props: any,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  hasForceUpdate = false;

  // 确保处理的更新队列的 work 是一个复制品
  queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);

  if (__DEV__) {
    currentlyProcessingQueue = queue;
  }

  // These values may change as we process the queue.
  // 当我们处理队列时,这些值可能会改变。
  let newBaseState = queue.baseState;
  let newFirstUpdate = null;
  let newExpirationTime = NoWork;

  // Iterate through the list of updates to compute the result.
  // 迭代更新列表以计算结果。
  let update = queue.firstUpdate;
  let resultState = newBaseState;
  while (update !== null) {
    const updateExpirationTime = update.expirationTime;
    if (updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      // 此更新没有足够的优先级。跳过它。
      if (newFirstUpdate === null) {
        // This is the first skipped update. It will be the first update in
        // the new list.
        // 这是第一个跳过的更新。这将是新列表中的第一个更新。
        newFirstUpdate = update;
        // Since this is the first update that was skipped, the current result
        // is the new base state.
        // 由于这是跳过的第一个更新,所以当前结果是 new base state。
        newBaseState = resultState;
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      // 由于此更新将保留在列表中,所以更新剩余的过期时间。
      if (newExpirationTime < updateExpirationTime) {
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      // 这次更新确实有足够的优先级。处理它并计算一个新的结果。
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      const callback = update.callback;
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        // 将其设置为null,以防在中止渲染期间发生突变。
        update.nextEffect = null;
        if (queue.lastEffect === null) {
          queue.firstEffect = queue.lastEffect = update;
        } else {
          queue.lastEffect.nextEffect = update;
          queue.lastEffect = update;
        }
      }
    }
    // Continue to the next update.
    // 继续下一个更新。
    update = update.next;
  }

  // Separately, iterate though the list of captured updates.
  // 另外,遍历捕获的更新列表。
  let newFirstCapturedUpdate = null;
  update = queue.firstCapturedUpdate;
  while (update !== null) {
    const updateExpirationTime = update.expirationTime;
    if (updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      // 这个更新没有足够的优先级。跳过它。
      if (newFirstCapturedUpdate === null) {
        // This is the first skipped captured update. It will be the first
        // update in the new list.
        // 这是第一次跳过捕获的更新。这将是新列表中的第一个更新。
        newFirstCapturedUpdate = update;
        // If this is the first update that was skipped, the current result is
        // the new base state.
        // 如果这是跳过的第一个更新,则当前结果是新的基本状态。
        if (newFirstUpdate === null) {
          newBaseState = resultState;
        }
      }
      // Since this update will remain in the list, update the remaining
      // expiration time.
      // 由于此更新将保留在列表中,所以更新剩余的过期时间。
      if (newExpirationTime < updateExpirationTime) {
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      // 这次更新确实有足够的优先级。处理它并计算一个新的结果。
      resultState = getStateFromUpdate(
        workInProgress,
        queue,
        update,
        resultState,
        props,
        instance,
      );
      const callback = update.callback;
      if (callback !== null) {
        workInProgress.effectTag |= Callback;
        // Set this to null, in case it was mutated during an aborted render.
        // 将其设置为 null,以防在中止 render 期间发生突变。
        update.nextEffect = null;
        if (queue.lastCapturedEffect === null) {
          queue.firstCapturedEffect = queue.lastCapturedEffect = update;
        } else {
          queue.lastCapturedEffect.nextEffect = update;
          queue.lastCapturedEffect = update;
        }
      }
    }
    update = update.next;
  }

  if (newFirstUpdate === null) {
    queue.lastUpdate = null;
  }
  if (newFirstCapturedUpdate === null) {
    queue.lastCapturedUpdate = null;
  } else {
    workInProgress.effectTag |= Callback;
  }
  if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
    // We processed every update, without skipping. That means the new base
    // state is the same as the result state.
    // 我们处理了每个更新,没有跳过。这意味着新的基状态与结果状态相同。
    newBaseState = resultState;
  }

  queue.baseState = newBaseState;
  queue.firstUpdate = newFirstUpdate;
  queue.firstCapturedUpdate = newFirstCapturedUpdate;

  // Set the remaining expiration time to be whatever is remaining in the queue.
  // This should be fine because the only two other things that contribute to
  // expiration time are props and context. We're already in the middle of the
  // begin phase by the time we start processing the queue, so we've already
  // dealt with the props. Context in components that specify
  // shouldComponentUpdate is tricky; but we'll have to account for
  // that regardless.
  // 将剩余的过期时间设置为队列中剩余的时间。
  // 这应该没问题,因为影响过期时间的另外两个因素是 props 和 context。
  // 在开始处理队列时,我们已经处于 begin 阶段的中间,
  // 所以我们已经处理了这些 props。
  // 指定 shouldComponentUpdate 的组件中的 Context 比较复杂;
  // 但无论如何我们都要考虑到这一点。
  workInProgress.expirationTime = newExpirationTime;
  workInProgress.memoizedState = resultState;

  if (__DEV__) {
    currentlyProcessingQueue = null;
  }
}

提交更新

提交更新

/**
 * 提交更新队列
 * @param finishedWork
 * @param finishedQueue
 * @param instance
 * @param renderExpirationTime
 */
export function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  // 如果已完成的渲染包含捕获的更新,
  // 并且仍然有较低优先级的更新遗留下来,
  // 那么我们需要将捕获的更新保存在队列中,
  // 以便在以较低优先级再次处理队列时重新基于它们,而不是丢弃它们。
  if (finishedQueue.firstCapturedUpdate !== null) {
    // 将捕获的更新列表连接到普通列表的末尾。
    if (finishedQueue.lastUpdate !== null) {
      finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
      finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
    }
    // 清除捕获的更新列表。
    finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
  }

  // 提交效果
  commitUpdateEffects(finishedQueue.firstEffect, instance);
  finishedQueue.firstEffect = finishedQueue.lastEffect = null;

  commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
  finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}

提交更新效果

/**
 * 提交更新效果
 * @param effect
 * @param instance
 */
function commitUpdateEffects<State>(
  effect: Update<State> | null,
  instance: any,
): void {
  while (effect !== null) {
    const callback = effect.callback;
    if (callback !== null) {
      effect.callback = null;
      callCallback(callback, instance);
    }
    effect = effect.nextEffect;
  }
}

其他方法

ensureWorkInProgressQueueIsAClone

/**
 * 确保工作中的处理队列是复制品
 *  1. 判断当前队列和更新队列是不是相等
 *  2. 若相等则克隆,若不等则返回当前队列
 * @param workInProgress
 * @param queue
 * @returns {UpdateQueue<State>}
 */
function ensureWorkInProgressQueueIsAClone<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
): UpdateQueue<State> {
  const current = workInProgress.alternate;
  if (current !== null) {
    // 如果正在工作的队列等于当前队列,我们需要首先克隆它。
    if (queue === current.updateQueue) {
      queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
    }
  }
  return queue;
}

cloneUpdateQueue

/**
 * 克隆更新队列
 * @param currentQueue
 * @returns {UpdateQueue<State>}
 */
function cloneUpdateQueue<State>(
  currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    baseState: currentQueue.baseState,
    firstUpdate: currentQueue.firstUpdate,
    lastUpdate: currentQueue.lastUpdate,

    // TODO: With resuming, if we bail out and resuse the child tree, we should
    // keep these effects.
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,

    firstEffect: null,
    lastEffect: null,

    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

enqueueUpdate

/**
 * 排队更新
 * @param fiber
 * @param update
 */
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // 更新队列是惰性创建的。
  const alternate = fiber.alternate;
  let queue1;
  let queue2;
  if (alternate === null) {
    // 只有一个 fiber
    queue1 = fiber.updateQueue;
    queue2 = null;
    if (queue1 === null) {
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // 有两个 owner。
    queue1 = fiber.updateQueue;
    queue2 = alternate.updateQueue;
    if (queue1 === null) {
      if (queue2 === null) {
        // Neither fiber has an update queue. Create new ones.
        // 这两种 fiber 都没有更新队列。创造一个新队列。
        queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        queue2 = alternate.updateQueue = createUpdateQueue(
          alternate.memoizedState,
        );
      } else {
        // Only one fiber has an update queue. Clone to create a new one.
        // 只有一个 fiber 有更新队列。克隆以创建一个新的。
        queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
      }
    } else {
      if (queue2 === null) {
        // Only one fiber has an update queue. Clone to create a new one.
        // 只有一个 fiber 有更新队列。克隆以创建一个新的。
        queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
      } else {
        // Both owners have an update queue.
        // 两个所有者都有一个更新队列。
      }
    }
  }
  if (queue2 === null || queue1 === queue2) {
    // There's only a single queue.
    // 只有一个队列。
    appendUpdateToQueue(queue1, update);
  } else {
    // There are two queues. We need to append the update to both queues,
    // while accounting for the persistent structure of the list — we don't
    // want the same update to be added multiple times.
    // 有两个队列。我们需要将更新附加到两个队列,
    // 同时考虑到列表的持久结构——我们不希望将相同的更新添加多次。
    if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
      // One of the queues is not empty. We must add the update to both queues.
      // 其中一个队列不是空的。我们必须将更新添加到两个队列。
      appendUpdateToQueue(queue1, update);
      appendUpdateToQueue(queue2, update);
    } else {
      // Both queues are non-empty. The last update is the same in both lists,
      // because of structural sharing. So, only append to one of the lists.
      // 两个队列都不是空的。由于结构共享,这两个列表中的最新更新是相同的。
      // 因此,只向其中一个列表追加。
      appendUpdateToQueue(queue1, update);
      // But we still need to update the `lastUpdate` pointer of queue2.
      // 但是我们仍然需要更新 queue2 的 `lastUpdate` 指针。
      queue2.lastUpdate = update;
    }
  }

  if (__DEV__) {
    if (
      fiber.tag === ClassComponent &&
      (currentlyProcessingQueue === queue1 ||
        (queue2 !== null && currentlyProcessingQueue === queue2)) &&
      !didWarnUpdateInsideUpdate
    ) {
      warningWithoutStack(
        false,
        'An update (setState, replaceState, or forceUpdate) was scheduled ' +
          'from inside an update function. Update functions should be pure, ' +
          'with zero side-effects. Consider using componentDidUpdate or a ' +
          'callback.',
      );
      didWarnUpdateInsideUpdate = true;
    }
  }
}

enqueueCapturedUpdate

/**
 * 排队捕获的更新
 * @param workInProgress
 * @param update
 */
export function enqueueCapturedUpdate<State>(
  workInProgress: Fiber,
  update: Update<State>,
) {
  // 捕获的更新进入一个单独的列表,并且只在正在进行的队列中。
  let workInProgressQueue = workInProgress.updateQueue;
  if (workInProgressQueue === null) {
    workInProgressQueue = workInProgress.updateQueue = createUpdateQueue(
      workInProgress.memoizedState,
    );
  } else {
    // TODO:我把它放在这里,而不是 createWorkInProgress,这样我们就不会不必要地克隆队列。也许有更好的方法来构造它。。
    workInProgressQueue = ensureWorkInProgressQueueIsAClone(
      workInProgress,
      workInProgressQueue,
    );
  }

  // Append the update to the end of the list.
  // 将更新追加到列表的末尾。
  if (workInProgressQueue.lastCapturedUpdate === null) {
    // This is the first render phase update
    // 这是第一个渲染阶段的更新
    workInProgressQueue.firstCapturedUpdate = workInProgressQueue.lastCapturedUpdate = update;
  } else {
    workInProgressQueue.lastCapturedUpdate.next = update;
    workInProgressQueue.lastCapturedUpdate = update;
  }
}

callCallback

/**
 * 调用回调
 * 1. 回调不存在则抛出错误
 * 2. 回调存在则使用上下文执行回调
 *
 * @param callback
 * @param context
 */
function callCallback(callback, context) {
  invariant(
    typeof callback === 'function',
    'Invalid argument passed as callback. Expected a function. Instead ' +
      'received: %s',
    callback,
  );
  callback.call(context);
}

遗留问题

  1. commitUpdateEffects 提交更新效果的时候是根据 Effect 效果的链表进行迭代的?这些 Update 的 nextEffect 是什么时候构成了链表结构?因为我没上面看到的更新队列只是一个 Update 使用 next 组成的一个链表结构
  2. commitUpdateQueue 提交的时候调用的也是 commitUpdateEffects,传入的 finishedQueue.firstEffect 和 finishedQueue.firstCapturedEffect,createUpdate 是在何处被调用创建了更新的?这些 Effect 又是些什么东西呢?
  3. 提交更新的时候为什么不是使用 Update.next 而是 Update.nextEffect 呢
  4. enqueueUpdate、enqueueCapturedUpdate、processUpdateQueue、createUpdate 在什么时候被调用