博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 深入学习:React 更新队列
阅读量:5952 次
发布时间:2019-06-19

本文共 17534 字,大约阅读时间需要 58 分钟。

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

更新

export type Update
= { expirationTime: ExpirationTime, // 到期时间 tag: 0 | 1 | 2 | 3, // 更新类型 payload: any, // 负载 callback: (() => mixed) | null, // 回调函数 next: Update
| null, // 下一个更新 nextEffect: Update
| 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
= { baseState: State, firstUpdate: Update
| null, lastUpdate: Update
| null, firstCapturedUpdate: Update
| null, lastCapturedUpdate: Update
| null, firstEffect: Update
| null, lastEffect: Update
| null, firstCapturedEffect: Update
| null, lastCapturedEffect: Update
| null,};复制代码

创建更新队列

/** * 创建更新队列 * @param baseState * @returns {UpdateQueue
} */export function createUpdateQueue
(baseState: State): UpdateQueue
{ const queue: UpdateQueue
= { 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
( queue: UpdateQueue
, update: Update
,) { // 将更新追加到列表的末尾。 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
( workInProgress: Fiber, queue: UpdateQueue
, update: Update
, 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 时可以接收一个更新器函数,这个更新器函数被绑定到当前的实例上运行,也就是在 中写到的,setState 可以接收一个函数作为参数:

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

处理更新

/** *  * @param workInProgress * @param queue * @param props * @param instance * @param renderExpirationTime */export function processUpdateQueue
( workInProgress: Fiber, queue: UpdateQueue
, 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
( finishedWork: Fiber, finishedQueue: UpdateQueue
, 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
( effect: Update
| 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
} */function ensureWorkInProgressQueueIsAClone
( workInProgress: Fiber, queue: UpdateQueue
,): UpdateQueue
{ const current = workInProgress.alternate; if (current !== null) { // 如果正在工作的队列等于当前队列,我们需要首先克隆它。 if (queue === current.updateQueue) { queue = workInProgress.updateQueue = cloneUpdateQueue(queue); } } return queue;}复制代码

cloneUpdateQueue

/** * 克隆更新队列 * @param currentQueue * @returns {UpdateQueue
} */function cloneUpdateQueue
( currentQueue: UpdateQueue
,): UpdateQueue
{ const queue: UpdateQueue
= { 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
(fiber: Fiber, update: Update
) { // 更新队列是惰性创建的。 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
( workInProgress: Fiber, update: Update
,) { // 捕获的更新进入一个单独的列表,并且只在正在进行的队列中。 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 在什么时候被调用

转载于:https://juejin.im/post/5d04251de51d4556be5b3a49

你可能感兴趣的文章
TFS简介
查看>>
docker管理平台 shipyard安装
查看>>
Bootstrap3 栅格系统-简介
查看>>
ADODB类库操作查询数据表
查看>>
博客搬家了
查看>>
Python中使用ElementTree解析xml
查看>>
sed处理文本
查看>>
jquery 操作iframe、frameset
查看>>
解决vim中不能使用小键盘
查看>>
jenkins权限管理,实现不同用户组显示对应视图views中不同的jobs
查看>>
我的友情链接
查看>>
CentOS定时同步系统时间
查看>>
批量删除用户--Shell脚本
查看>>
如何辨别android开发包的安全性
查看>>
Eclipse Java @Override 报错
查看>>
知道双字节码, 如何获取汉字 - 回复 "pinezhou" 的问题
查看>>
linux下lvs搭建负载均衡集群
查看>>
JMS 实例讲解
查看>>
求教:如何成为一个优秀的项目经理人
查看>>
shell 脚本--备份、还原mysql数据库
查看>>