制作ppt的网站,品牌策划大赛获奖案例,免费咨询法律平台,哪些网站可以做外链文章主要解释了 React 如何处理状态更新#xff0c;特别是其双缓冲机制和优先级调度。 文章内容结合 deepseek 进行了汇总 一、UpdateQueue 的结构与双缓冲机制
1. 更新队列是一个链表
每个 Update 代表一个状态更新#xff08;如 setState#xff09;。队列按插入顺序存储…文章主要解释了 React 如何处理状态更新特别是其双缓冲机制和优先级调度。文章内容结合 deepseek 进行了汇总一、UpdateQueue 的结构与双缓冲机制1. 更新队列是一个链表每个 Update 代表一个状态更新如 setState。队列按插入顺序存储更新而非按优先级排序。2. 双队列设计current 与 work-in-progressReact 为每个组件维护两个队列current queue对应已提交到 DOM 的当前状态。work-in-progress queue对应正在进行的渲染状态可异步计算。关键行为当开始一次新的渲染时会克隆 current queue 作为 work-in-progress queue 的初始值。当提交commit时work-in-progress queue 会成为新的 current queue。如果渲染被中断并丢弃则基于 current queue 重新创建 work-in-progress queue。3. 为什么更新要同时追加到两个队列如果只追加到 work-in-progress queue当渲染被丢弃并重新克隆 current queue 时新更新会丢失。如果只追加到 current queue当 work-in-progress queue 提交并覆盖 current queue 时新更新也会丢失。同时追加到两个队列保证更新一定会被下一次渲染处理且不会重复应用。二、优先级处理机制1. 更新按插入顺序存储但按优先级处理更新在链表中按插入顺序排列不按优先级排序。处理队列时只处理优先级足够的更新。如果某个更新因优先级不足被跳过它之后的所有更新都会保留即使它们优先级足够。2. base state 的作用base state 是队列中第一个更新之前的状态。当跳过某些更新时后续的高优先级更新会基于新的 base state 重新计算rebase。3. 举例说明注释中的例子初始状态‘’更新队列字母为更新内容数字为优先级A1 - B2 - C1 - D2第一次渲染优先级 1base state: ‘’可处理的更新A1优先级1、C1优先级1跳过 B2优先级不足结果状态‘AC’注意C1 是基于 ‘A’ 状态处理的但 B2 被跳过所以 B2 和 D2 留在队列中。第二次渲染优先级 2base state: ‘A’跳过的 B2 之前的状态队列中剩余更新B2、C1、D2可处理的更新B2、C1、D2优先级都满足结果状态‘ABCD’关键点C1 被处理了两次在两个优先级下但最终状态是一致的。最终结果与按顺序同步处理所有更新相同‘ABCD’。三、设计哲学与优势1. 确定性最终状态无论更新优先级如何、中间是否被中断最终状态总是与同步顺序处理所有更新一致。这是 React 可预测性的核心保证。2. 时间切片与并发渲染的支撑允许高优先级更新打断低优先级渲染。被跳过的更新和后续更新会被保留在更低优先级时处理。3. 性能与响应性平衡高优先级更新如用户输入可快速响应。低优先级更新如数据拉取可等待或被打断。四、实际应用中的体现这种机制在 React 特性中体现为自动批处理多个 setState 合并为一个更新。并发特性Concurrent Mode高优先级更新可打断低优先级渲染。Suspense数据获取更新可被推迟。五、简单总结特点说明双缓冲队列current已提交和 work-in-progress计算中保证更新不丢失。插入顺序存储更新按调用顺序追加到链表尾部不按优先级排序。优先级筛选处理处理时跳过优先级不足的更新但保留后续所有更新。base state 重定基跳过更新时后续更新基于新的 base state 重新计算。。确定性结果最终状态与同步处理所有更新一致中间状态可能不同。。这种设计让 React 在异步、可中断的渲染过程中既能保证最终状态的一致性又能实现优先级调度优化用户体验。[附] ReactUpdateQueue.js 代码如下/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * flow */// UpdateQueue is a linked list of prioritized updates.//// Like fibers, update queues come in pairs: a current queue, which represents// the visible state of the screen, and a work-in-progress queue, which is// can be mutated and processed asynchronously before it is committed — a form// of double buffering. If a work-in-progress render is discarded before// finishing, we create a new work-in-progress by cloning the current queue.//// Both queues share a persistent, singly-linked list structure. To schedule an// update, we append it to the end of both queues. Each queue maintains a// pointer to first update in the persistent list that hasnt been processed.// The work-in-progress pointer always has a position equal to or greater than// the current queue, since we always work on that one. The current queues// pointer is only updated during the commit phase, when we swap in the// work-in-progress.//// For example://// Current pointer: A - B - C - D - E - F// Work-in-progress pointer: D - E - F// ^// The work-in-progress queue has// processed more updates than current.//// The reason we append to both queues is because otherwise we might drop// updates without ever processing them. For example, if we only add updates to// the work-in-progress queue, some updates could be lost whenever a work-in// -progress render restarts by cloning from current. Similarly, if we only add// updates to the current queue, the updates will be lost whenever an already// in-progress queue commits and swaps with the current queue. However, by// adding to both queues, we guarantee that the update will be part of the next// work-in-progress. (And because the work-in-progress queue becomes the// current queue once it commits, theres no danger of applying the same// update twice.)//// Prioritization// --------------//// Updates are not sorted by priority, but by insertion; new updates are always// appended to the end of the list.//// The priority is still important, though. When processing the update queue// during the render phase, only the updates with sufficient priority are// included in the result. If we skip an update because it has insufficient// priority, it remains in the queue to be processed later, during a lower// priority render. Crucially, all updates subsequent to a skipped update also// remain in the queue *regardless of their priority*. That means high priority// updates are sometimes processed twice, at two separate priorities. We also// keep track of a base state, that represents the state before the first// update in the queue is applied.//// For example://// Given a base state of , and the following queue of updates//// A1 - B2 - C1 - D2//// where the number indicates the priority, and the update is applied to the// previous state by appending a letter, React will process these updates as// two separate renders, one per distinct priority level://// First render, at priority 1:// Base state: // Updates: [A1, C1]// Result state: AC//// Second render, at priority 2:// Base state: A - The base state does not include C1,// because B2 was skipped.// Updates: [B2, C1, D2] - C1 was rebased on top of B2// Result state: ABCD//// Because we process updates in insertion order, and rebase high priority// updates when preceding updates are skipped, the final result is deterministic// regardless of priority. Intermediate state may vary according to system// resources, but the final state is always the same.importtype{Fiber}from./ReactFiber;importtype{ExpirationTime}from./ReactFiberExpirationTime;import{NoWork}from./ReactFiberExpirationTime;import{Callback,ShouldCapture,DidCapture}fromshared/ReactSideEffectTags;import{ClassComponent}fromshared/ReactWorkTags;import{debugRenderPhaseSideEffects,debugRenderPhaseSideEffectsForStrictMode,}fromshared/ReactFeatureFlags;import{StrictMode}from./ReactTypeOfMode;importinvariantfromshared/invariant;importwarningWithoutStackfromshared/warningWithoutStack;exporttype UpdateState{expirationTime:ExpirationTime,tag:0|1|2|3,payload:any,callback:(()mixed)|null,next:UpdateState|null,nextEffect:UpdateState|null,};exporttype UpdateQueueState{baseState:State,firstUpdate:UpdateState|null,lastUpdate:UpdateState|null,firstCapturedUpdate:UpdateState|null,// ErrorBoundary 异常捕获使用lastCapturedUpdate:UpdateState|null,firstEffect:UpdateState|null,lastEffect:UpdateState|null,firstCapturedEffect:UpdateState|null,lastCapturedEffect:UpdateState|null,};exportconstUpdateState0;exportconstReplaceState1;exportconstForceUpdate2;exportconstCaptureUpdate3;// Global state that is reset at the beginning of calling processUpdateQueue.// It should only be read right after calling processUpdateQueue, via// checkHasForceUpdateAfterProcessing.lethasForceUpdatefalse;letdidWarnUpdateInsideUpdate;letcurrentlyProcessingQueue;exportletresetCurrentlyProcessingQueue;if(__DEV__){didWarnUpdateInsideUpdatefalse;currentlyProcessingQueuenull;resetCurrentlyProcessingQueue(){currentlyProcessingQueuenull;};}exportfunctioncreateUpdateQueueState(baseState:State):UpdateQueueState{constqueue:UpdateQueueState{baseState,firstUpdate:null,lastUpdate:null,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null,};returnqueue;}functioncloneUpdateQueueState(currentQueue:UpdateQueueState,):UpdateQueueState{constqueue:UpdateQueueState{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,};returnqueue;}exportfunctioncreateUpdate(expirationTime:ExpirationTime):Update*{return{expirationTime:expirationTime,tag:UpdateState,payload:null,callback:null,next:null,nextEffect:null,};}functionappendUpdateToQueueState(queue:UpdateQueueState,update:UpdateState,){// Append the update to the end of the list.if(queue.lastUpdatenull){// Queue is emptyqueue.firstUpdatequeue.lastUpdateupdate;}else{queue.lastUpdate.nextupdate;queue.lastUpdateupdate;}}exportfunctionenqueueUpdateState(fiber:Fiber,update:UpdateState){// Update queues are created lazily.constalternatefiber.alternate;letqueue1;letqueue2;if(alternatenull){// Theres only one fiber.queue1fiber.updateQueue;queue2null;if(queue1null){queue1fiber.updateQueuecreateUpdateQueue(fiber.memoizedState);}}else{// There are two owners.queue1fiber.updateQueue;queue2alternate.updateQueue;if(queue1null){if(queue2null){// Neither fiber has an update queue. Create new ones.queue1fiber.updateQueuecreateUpdateQueue(fiber.memoizedState);queue2alternate.updateQueuecreateUpdateQueue(alternate.memoizedState,);}else{// Only one fiber has an update queue. Clone to create a new one.queue1fiber.updateQueuecloneUpdateQueue(queue2);}}else{if(queue2null){// Only one fiber has an update queue. Clone to create a new one.queue2alternate.updateQueuecloneUpdateQueue(queue1);}else{// Both owners have an update queue.}}}if(queue2null||queue1queue2){// Theres 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 dont// want the same update to be added multiple times.if(queue1.lastUpdatenull||queue2.lastUpdatenull){// 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.lastUpdateupdate;}}if(__DEV__){if(fiber.tagClassComponent(currentlyProcessingQueuequeue1||(queue2!nullcurrentlyProcessingQueuequeue2))!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.,);didWarnUpdateInsideUpdatetrue;}}}exportfunctionenqueueCapturedUpdateState(workInProgress:Fiber,update:UpdateState,){// Captured updates go into a separate list, and only on the work-in-// progress queue.letworkInProgressQueueworkInProgress.updateQueue;if(workInProgressQueuenull){workInProgressQueueworkInProgress.updateQueuecreateUpdateQueue(workInProgress.memoizedState,);}else{// TODO: I put this here rather than createWorkInProgress so that we dont// clone the queue unnecessarily. Theres probably a better way to// structure this.workInProgressQueueensureWorkInProgressQueueIsAClone(workInProgress,workInProgressQueue,);}// Append the update to the end of the list.if(workInProgressQueue.lastCapturedUpdatenull){// This is the first render phase updateworkInProgressQueue.firstCapturedUpdateworkInProgressQueue.lastCapturedUpdateupdate;}else{workInProgressQueue.lastCapturedUpdate.nextupdate;workInProgressQueue.lastCapturedUpdateupdate;}}functionensureWorkInProgressQueueIsACloneState(workInProgress:Fiber,queue:UpdateQueueState,):UpdateQueueState{constcurrentworkInProgress.alternate;if(current!null){// If the work-in-progress queue is equal to the current queue,// we need to clone it first.if(queuecurrent.updateQueue){queueworkInProgress.updateQueuecloneUpdateQueue(queue);}}returnqueue;}functiongetStateFromUpdateState(workInProgress:Fiber,queue:UpdateQueueState,update:UpdateState,prevState:State,nextProps:any,instance:any,):any{switch(update.tag){caseReplaceState:{constpayloadupdate.payload;if(typeofpayloadfunction){// Updater functionif(__DEV__){if(debugRenderPhaseSideEffects||(debugRenderPhaseSideEffectsForStrictModeworkInProgress.modeStrictMode)){payload.call(instance,prevState,nextProps);}}returnpayload.call(instance,prevState,nextProps);}// State objectreturnpayload;}caseCaptureUpdate:{workInProgress.effectTag(workInProgress.effectTag~ShouldCapture)|DidCapture;}// Intentional fallthroughcaseUpdateState:{constpayloadupdate.payload;letpartialState;if(typeofpayloadfunction){// Updater functionif(__DEV__){if(debugRenderPhaseSideEffects||(debugRenderPhaseSideEffectsForStrictModeworkInProgress.modeStrictMode)){payload.call(instance,prevState,nextProps);}}partialStatepayload.call(instance,prevState,nextProps);}else{// Partial state objectpartialStatepayload;}if(partialStatenull||partialStateundefined){// Null and undefined are treated as no-ops.returnprevState;}// Merge the partial state and the previous state.returnObject.assign({},prevState,partialState);}caseForceUpdate:{hasForceUpdatetrue;returnprevState;}}returnprevState;}exportfunctionprocessUpdateQueueState(workInProgress:Fiber,queue:UpdateQueueState,props:any,instance:any,renderExpirationTime:ExpirationTime,):void{hasForceUpdatefalse;queueensureWorkInProgressQueueIsAClone(workInProgress,queue);if(__DEV__){currentlyProcessingQueuequeue;}// These values may change as we process the queue.letnewBaseStatequeue.baseState;letnewFirstUpdatenull;letnewExpirationTimeNoWork;// Iterate through the list of updates to compute the result.letupdatequeue.firstUpdate;letresultStatenewBaseState;while(update!null){constupdateExpirationTimeupdate.expirationTime;if(updateExpirationTimerenderExpirationTime){// This update does not have sufficient priority. Skip it.if(newFirstUpdatenull){// This is the first skipped update. It will be the first update in// the new list.newFirstUpdateupdate;// Since this is the first update that was skipped, the current result// is the new base state.newBaseStateresultState;}// Since this update will remain in the list, update the remaining// expiration time.if(newExpirationTimeNoWork||newExpirationTimeupdateExpirationTime){newExpirationTimeupdateExpirationTime;}}else{// This update does have sufficient priority. Process it and compute// a new result.resultStategetStateFromUpdate(workInProgress,queue,update,resultState,props,instance,);constcallbackupdate.callback;if(callback!null){workInProgress.effectTag|Callback;// Set this to null, in case it was mutated during an aborted render.update.nextEffectnull;if(queue.lastEffectnull){queue.firstEffectqueue.lastEffectupdate;}else{queue.lastEffect.nextEffectupdate;queue.lastEffectupdate;}}}// Continue to the next update.updateupdate.next;}// Separately, iterate though the list of captured updates.letnewFirstCapturedUpdatenull;updatequeue.firstCapturedUpdate;while(update!null){constupdateExpirationTimeupdate.expirationTime;if(updateExpirationTimerenderExpirationTime){// This update does not have sufficient priority. Skip it.if(newFirstCapturedUpdatenull){// This is the first skipped captured update. It will be the first// update in the new list.newFirstCapturedUpdateupdate;// If this is the first update that was skipped, the current result is// the new base state.if(newFirstUpdatenull){newBaseStateresultState;}}// Since this update will remain in the list, update the remaining// expiration time.if(newExpirationTimeNoWork||newExpirationTimeupdateExpirationTime){newExpirationTimeupdateExpirationTime;}}else{// This update does have sufficient priority. Process it and compute// a new result.resultStategetStateFromUpdate(workInProgress,queue,update,resultState,props,instance,);constcallbackupdate.callback;if(callback!null){workInProgress.effectTag|Callback;// Set this to null, in case it was mutated during an aborted render.update.nextEffectnull;if(queue.lastCapturedEffectnull){queue.firstCapturedEffectqueue.lastCapturedEffectupdate;}else{queue.lastCapturedEffect.nextEffectupdate;queue.lastCapturedEffectupdate;}}}updateupdate.next;}if(newFirstUpdatenull){queue.lastUpdatenull;}if(newFirstCapturedUpdatenull){queue.lastCapturedUpdatenull;}else{workInProgress.effectTag|Callback;}if(newFirstUpdatenullnewFirstCapturedUpdatenull){// We processed every update, without skipping. That means the new base// state is the same as the result state.newBaseStateresultState;}queue.baseStatenewBaseState;queue.firstUpdatenewFirstUpdate;queue.firstCapturedUpdatenewFirstCapturedUpdate;// 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. Were already in the middle of the// begin phase by the time we start processing the queue, so weve already// dealt with the props. Context in components that specify// shouldComponentUpdate is tricky; but well have to account for// that regardless.workInProgress.expirationTimenewExpirationTime;workInProgress.memoizedStateresultState;if(__DEV__){currentlyProcessingQueuenull;}}functioncallCallback(callback,context){invariant(typeofcallbackfunction,Invalid argument passed as callback. Expected a function. Instead received: %s,callback,);callback.call(context);}exportfunctionresetHasForceUpdateBeforeProcessing(){hasForceUpdatefalse;}exportfunctioncheckHasForceUpdateAfterProcessing():boolean{returnhasForceUpdate;}exportfunctioncommitUpdateQueueState(finishedWork:Fiber,finishedQueue:UpdateQueueState,instance:any,renderExpirationTime:ExpirationTime,):void{// If the finished render included captured updates, and there are still// lower priority updates left over, we need to keep the captured updates// in the queue so that they are rebased and not dropped once we process the// queue again at the lower priority.if(finishedQueue.firstCapturedUpdate!null){// Join the captured update list to the end of the normal list.if(finishedQueue.lastUpdate!null){finishedQueue.lastUpdate.nextfinishedQueue.firstCapturedUpdate;finishedQueue.lastUpdatefinishedQueue.lastCapturedUpdate;}// Clear the list of captured updates.finishedQueue.firstCapturedUpdatefinishedQueue.lastCapturedUpdatenull;}// Commit the effectscommitUpdateEffects(finishedQueue.firstEffect,instance);finishedQueue.firstEffectfinishedQueue.lastEffectnull;commitUpdateEffects(finishedQueue.firstCapturedEffect,instance);finishedQueue.firstCapturedEffectfinishedQueue.lastCapturedEffectnull;}functioncommitUpdateEffectsState(effect:UpdateState|null,instance:any,):void{while(effect!null){constcallbackeffect.callback;if(callback!null){effect.callbacknull;callCallback(callback,instance);}effecteffect.nextEffect;}}至此结束。