北京网站建设哪个好,单职业传奇网站,扬中网站建设公司,WordPress tips在 react 项目中#xff0c;setState 被用于更新 state#xff0c;从而实现组件重新渲染更新。经过查找阅读许多资料以及源码后#xff0c;本文就来个人总结一下#xff0c;简要解析 react 在 setState 后是如何更新组件的。
前言#xff1a;setState 的同步和异步
异步…在 react 项目中setState 被用于更新 state从而实现组件重新渲染更新。经过查找阅读许多资料以及源码后本文就来个人总结一下简要解析 react 在 setState 后是如何更新组件的。
前言setState 的同步和异步
异步setState 一般情况下是异步的由 react 的批量更新事务(ReactDefaultBatchingStrategy)控制(即 react 控制的事件非调用 js 原生事件时是异步的)以及生命周期函数调用 setState 也不会同步更新 state。同一个函数中执行多个 setState 时会合并并且同一个属性以最后一次 setState 的值为准。同步setState 在 setTimeoutsetInterval 和 js 原生事件中被调用则是同步的。
原理同步和异步是由什么控制的? 是通过 batchedUpdates 函数中设置 isBatchingUpdates 为 true 时则 setState 为异步更新false 时为同步更新。
react15
先介绍事务ReactDefaultBatchingStrategyTransaction和 batchedUpdates 函数用来启动事务的,启动事务后 setState 就是异步更新了)后介绍setState。
ReactDefaultBatchingStrategyTransaction 事务 setState 依靠事务进行更新事务生命周期包含 initialize、perform、close 阶段。在开启事务后遇到 setState 后 则将 partial state 存到组件实例的_pendingStateQueue 上, 接着调用 enqueueUpdate 排队更新方法如下 setState 后解析。 // 批处理策略
var ReactDefaultBatchingStrategy {isBatchingUpdates: false,batchedUpdates: function(callback, a, b, c, d, e) {var alreadyBatchingUpdates ReactDefaultBatchingStrategy.isBatchingUpdates;ReactDefaultBatchingStrategy.isBatchingUpdates true; //开启一次batchif (alreadyBatchingUpdates) {return callback(a, b, c, d, e);} else {// 启动事务, 将callback放进事务里执行return transaction.perform(callback, null, a, b, c, d, e);}},
};batchUpdates batchedUpdates先设置 isBatchingUpdates为true(ReactDefaultBatchingStrategy.isBatchingUpdates true)开启事务后将 callback函数放进事务里执行(transaction.perform(callback,…))无论你传进去的函数是什么, 无论这个函数后续会做什么, 都会在执行完 callback(setState 的第二个参数)后调用事务的 close 方法 在 React 中,调用batchedUpdates有很多地方 第一种情况首次渲染组件时(源码在ReactMount.js里调用了ReactUpdates.batchedUpdates) 第二种情况元素上或者组件上绑定了react控制的事件非调用js原生事件事件的监听函数中调用setState。(源码在ReactEventListener.js里react事件系统中的dispatchEvent函数启动了事务(调用了ReactUpdates.batchedUpdates)) 重点setState后
1、setState后
调用updater的enqueueSetState方法把需要更新的state(partial state)push进去等待队列_pendingStateQueue中, 接着调用enqueueUpdate排队更新。
2、enqueueUpdate
enqueueUpdate方法里需要判断batchingStrategy.isBatchingUpdates true即是否开启batch事务
情况一如果已经开启batch然后标记当前组件为dirtyComponent, 存到dirtyComponents数组中等到 ReactDefaultBatchingStrategy事务结束时(close)调用runBatchedUpdates批量更新所有组件情况二方法中如果没有开启batch(或当前batch已结束也就是说在事务的initialize或更新阶段)就调用batchedUpdates函数开启一次batch再重新执行enqueueUpdate方法判断isBatchingUpdates现在为true了标记当前组件为dirtyComponent, 存到dirtyComponents数组中, 并没有立即更新而是继续执行后面事情等到 ReactDefaultBatchingStrategy事务结束时(close)调用flushBatchedUpdates函数runBatchedUpdates函数批量更新所有组件(第三点)
// ReactBaseClasses.js :
ReactComponent.prototype.setState function(partialState, callback) {this.updater.enqueueSetState(this, partialState);if (callback) {// 如果有callback就将callback放入回调函数队列this.updater.enqueueCallback(this, callback, setState);}
};// ReactUpdateQueue.js:
enqueueSetState: function(publicInstance, partialState) {// 根据 this.setState 中的 this 拿到内部实例, 也就是组件实例var internalInstance getInternalInstanceReadyForUpdate(publicInstance, setState);// 取得组件实例的_pendingStateQueuevar queue internalInstance._pendingStateQueue ||(internalInstance._pendingStateQueue []);// 将partial state存到_pendingStateQueuequeue.push(partialState);// 调用enqueueUpdateenqueueUpdate(internalInstance);}// ReactUpdate.js:
function enqueueUpdate(component) {ensureInjected(); // 注入默认策略// 如果没有开启batch(或当前batch已结束)就开启一次batch再执行, 这通常发生在异步回调中调用 setState // 的情况if (!batchingStrategy.isBatchingUpdates) {batchingStrategy.batchedUpdates(enqueueUpdate, component);return;}// 如果batch已经开启就存储更新dirtyComponents.push(component);if (component._updateBatchNumber null) {component._updateBatchNumber updateBatchNumber 1;}
}3、事务结束时(close阶段)
批量更新的阶段调用flushBatchedUpdates函数启动ReactUpdatesFlushTransaction事务负责批量更新这个事务执行了runBatchedUpdates方法通过遍历dirtyComponents数组(在函数里ReactReconciler.performUpdateIfNecessary中调用updateComponent更新组件)进行批量更新。再结束本次batch事务(即ReactDefaultBatchingStrategy.isBatchingUpdates false; )
var flushBatchedUpdates function () {// 启动批量更新事务while (dirtyComponents.length || asapEnqueued) {if (dirtyComponents.length) {var transaction ReactUpdatesFlushTransaction.getPooled();transaction.perform(runBatchedUpdates, null, transaction);ReactUpdatesFlushTransaction.release(transaction);}
// 批量处理callbackif (asapEnqueued) {asapEnqueued false;var queue asapCallbackQueue;asapCallbackQueue CallbackQueue.getPooled();queue.notifyAll();CallbackQueue.release(queue);}}
};// ReactUpdates.js
function runBatchedUpdates(transaction) {var len transaction.dirtyComponentsLength;// 排序保证父组件优先于子组件更新dirtyComponents.sort(mountOrderComparator);// 代表批量更新的次数, 保证每个组件只更新一次updateBatchNumber;// 遍历 dirtyComponentsfor (var i 0; i len; i) {var component dirtyComponents[i];var callbacks component._pendingCallbacks;component._pendingCallbacks null;...// 执行更新ReactReconciler.performUpdateIfNecessary(component,transaction.reconcileTransaction,updateBatchNumber,);...// 存储 callback以便后续按顺序调用if (callbacks) {for (var j 0; j callbacks.length; j) {transaction.callbackQueue.enqueue(callbacks[j],component.getPublicInstance(),);}}}
}4、updateComponent
ReactReconciler.performUpdateIfNecessary中调用updateComponent更新组件 performUpdateIfNecessary: function (transaction) {if (this._pendingElement ! null) {ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);} else if (this._pendingStateQueue ! null || this._pendingForceUpdate) {this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);} else {this._updateBatchNumber null;}
}5、updateComponent
react内部有3种不同组件ReactCompositeComponent、ReactDOMComponent和ReactDOMTextComponent。
ReactCompositeComponentre-render, 与之前 render 的 element 比较, 如果两者key element.type 相等, 则进入下一层进行更新; 如果不等, 直接移除重新mountReactDOMComponent render前生成新的虚拟DOM对前后虚拟DOM进行diff算法比较(传送门react的diff算法)找出最小更新部分批量更新ReactDOMTextComponent直接更新Text
总结
react16加入Fiber
react16版本中加入了Fiber架构(传送门Fiber简介) Fiber主要分两个阶段 调度阶段reconciliationFiber调度 将一个state更新任务拆分成多个时间小片形成一个 Fiber 任务队列.在任务队列中选出优先级高的 Fiber 执行如果执行时间超过了deathLine则设置为pending状态挂起状态即执行一段时间会跳出找Fiber任务队列中更高级的任务如果有就放弃当前任务即使当前任务执行了一半可能已经经历了一些生命周期都会被打断从来。 渲染阶段commit 进入render函数构建真实的virtualDomTreeReact将其所有的变更一次性更新到DOM上。 重点setState后
调用updater的enqueueSetState方法传入state, callback
Component.prototype.setState function(partialState, callback) {this.updater.enqueueSetState(this, partialState, callback, setState);
};updater更新器中的enqueueSetState
var updater {...enqueueSetState(instance, partialState, callback) {// 获取到当前组件实例上的fiberconst fiber ReactInstanceMap.get(instance);callback callback undefined ? null : callback;// 计算当前fiber的到期时间即计算出优先级const expirationTime computeExpirationForFiber(fiber);const update {expirationTime,partialState,callback,isReplace: false,isForced: false,capturedValue: null,next: null,};// 把更新信息任务插入fiber的update queue更新队列中insertUpdateIntoFiber(fiber, update);// 开启调度任务进入fiber的调度阶段reconciliationscheduleWork(fiber, expirationTime);}...
};enqueueSetState详解
1、get获取组件实例上的fiber
function get(key) {return key._reactInternalFiber;
}2、计算到期时间/优先级computeExpirationForFiber 计算当前fiber的优先级(即过期时间),expirationTime 优先级 expirationTime 不为 1 的时候则其值越低优先级越高。Fiber任务的优先级文本框输入 本次调度结束需完成的任务 动画过渡 交互反馈 数据更新 不会显示但以防将来会显示的任务。如下 //用来计算fiber的到期时间到期时间用来表示任务的优先级。function computeExpirationForFiber(fiber) {var expirationTime void 0;if (expirationContext ! NoWork) {// expirationTime expirationContext;} else if (isWorking) {if (isCommitting) {//同步模式立即处理任务默认是1expirationTime Sync;} else {//渲染阶段的更新应该与正在渲染的工作同时过期。expirationTime nextRenderExpirationTime;}} else {//没有到期时间的情况下创建一个到期时间if (fiber.mode AsyncMode) {if (isBatchingInteractiveUpdates) {// 这是一个交互式更新var currentTime recalculateCurrentTime();expirationTime computeInteractiveExpiration(currentTime);} else {// 这是一个异步更新var _currentTime recalculateCurrentTime();expirationTime computeAsyncExpiration(_currentTime);}} else {// 这是一个同步更新expirationTime Sync;}}if (isBatchingInteractiveUpdates) {//这是一个交互式的更新。跟踪最低等待交互过期时间。这允许我们在需要时同步刷新所有交互更新。if (lowestPendingInteractiveExpirationTime NoWork || expirationTime lowestPendingInteractiveExpirationTime) {lowestPendingInteractiveExpirationTime expirationTime;}}return expirationTime;}fiber优先级定义
module.exports { // heigh levelNoWork: 0, // No work is pending.SynchronousPriority: 1, // For controlled text inputs. TaskPriority: 2, // Completes at the end of the current tick.AnimationPriority: 3, // Needs to complete before the next frame.// low levelHighPriority: 4, // Interaction that needs to complete pretty soon to feel responsive.LowPriority: 5, // Data fetching, or result from updating stores.OffscreenPriority: 6, // Wont be visible but do the work in case it becomes visible.
};3、insertUpdateIntoFiber(fiber, update) 把更新信息任务插入update queue更新队列中。确保更新队列存在不存在则调用ensureUpdateQueues(fiber)创建一个fiber队列,调用insertUpdateIntoQueue(queue, update)将update的值更新到queue队列中。 function insertUpdateIntoFiber(fiber, update) {//确保更新队列存在不存在则创建ensureUpdateQueues(fiber);//上一步已经将q1和q2队列进行了处理定义2个局部变量queue1和queue2来保存队列信息。var queue1 q1;var queue2 q2;// 如果只有一个队列请将更新添加到该队列并退出。if (queue2 null) {insertUpdateIntoQueue(queue1, update);return;}// 如果任一队列为空我们需要添加到两个队列中。if (queue1.last null || queue2.last null) {//将update的值更新到队列1和队列2上然后退出该函数insertUpdateIntoQueue(queue1, update);insertUpdateIntoQueue(queue2, update);return;}// 如果两个列表都不为空则由于结构共享两个列表的最后更新都是相同的。所以我们应该只追加到其中一个列表。insertUpdateIntoQueue(queue1, update);// 但是我们仍然需要更新queue2的last指针。queue2.last update;
}4、scheduleWork(fiber, expirationTime) 开启调度任务进入fiber的调度阶段 scheduleWork执行流程scheduleWork scheduleWorkImpl requestWork 同步/异步 performSyncWork performWork performWorkOnRoot renderRoot/completeRoot workLoop performUnitOfWork beginWork/completeUnitOfWork updateClassComponent reconcileChildrenAtExpirationTime reconcileChildFibers reconcileChildrenArray scheduleWork执行流程比较长下面讲下主要步骤(详细步骤代码) scheduleWorkImpl 调用scheduleWorkImpl(fiber, expirationTime, false)函数更新每个node的优先级(即将一个state更新任务拆分成多个时间小片形成一个 Fiber 任务队列) requestWork 同步执行performSyncWork异步执行scheduleCallbackWithExpiration scheduleCallbackWithExpiration会调浏览器的requestidlecallback在浏览器空闲的时候进行处理。 expirationTime同步执行任务下expirationTime为0即nowork0。不为0时使用requestidlecallback/requestAnimationFrame异步执行任务 performSyncWork 主要的任务调度 这里会找到高优任务先执行。 同步任务会直接调用performWorkOnRoot进行下一步 异步任务也会调performWorkOnRoot但处理不太一样 如果有上次遗留的任务留到空闲时运行 workLoopfiber里的判断时间片超过deathLine则设置为pending状态 异步任务在处理的时候会调用shouldYieldshouldYield会判断是不是已经超时了超时暂时先不做。 performUnitOfWork reconcilation阶段 调用beginWork处理组件针对不同组件不同处理。此过程包括dom diff(生成新的 Virtual DOM然后通过 Diff 算法快速找出需要更新的元素放到更新队列中去得到新的更新队列)。然后调用completeUnitOfWork对begin work产生的effect list进行一些处理包括对ReactDOMComponent DOM组件的更新。
渲染阶段commit 进入render函数构建真实的virtualDomTree调用completeRoot/commitRootReact将其所有的变更一次性更新到DOM上。 本文参考
从源码全面剖析 React 组件更新机制 React16——看看setState过程中fiber干了什么事情 react 16 渲染整理