React 深入学习:React 更新队列

栏目: IOS · Android · 发布时间: 6年前

内容简介:React 的状态更新分为四种情况,他们分别对应 Update 的 tag 属性的四个值:调用此方法创建的更新默认为是局部更新,需要合并前后状态。从上面的代码中可以看到,更新队列是一个单向链表:
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;
}
复制代码

数据结构

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

React 深入学习:React 更新队列

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 在什么时候被调用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computer Age Statistical Inference

Computer Age Statistical Inference

Bradley Efron、Trevor Hastie / Cambridge University Press / 2016-7-21 / USD 74.99

The twenty-first century has seen a breathtaking expansion of statistical methodology, both in scope and in influence. 'Big data', 'data science', and 'machine learning' have become familiar terms in ......一起来看看 《Computer Age Statistical Inference》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具