V15
v15中的整个应用基于事务来实现挂载更新等操作
v15中同一个事物中多次调用setState
会被合并为一个state,并对最新的state走一次更新流程。
所有的setState 存放在对象的_pendingStateQueue
属性上,在多次调用时:
// 合并state
var nextState = Object.assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function'
? partial.call(inst, nextState, props, context)
: partial,
);
}
// 返回nextState, 浅合并
批量更新,在被react管控的事件中,比如mount、event等类型中产生的setState会被执行批量更新。
全局根据isBatchingUpdates
来进行更新处理,相当于一把锁, 在被管控事件中会锁上,在事务的close阶段把锁打开。
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* 唯一的更新函数,开启批量更新
*/
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
处于事务中的setState的更新时被管控的,所以会进入一个数组中等待
function enqueueUpdate(component) {
// 第一次,开启批量更新
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 处于更新中,进入队列中等待
// dirtyComponents 存放在一次事务中等待被更新的组件实例
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
didmount事件遇到时会加入callbakcQueue
中等待,在此次事务的close阶段,会被从后向前执行,也就是先执行子组件的didmount
后执行父组件的
比如在componentDidMount
中发起的setState。在初次挂载时react会手动开启批量更新,所以didmount中的setState会被加入的_pendingStateQueue
中存放,实例组件进入数组中等待,在手动开启批量更新,发起的事务的close阶段,这些state才会被重新计算后组件更新。
v15中的批量更新由事务来实现,全局的更新由isBatchingUpdates
来管控。 该变量的修改只在首次挂载时和产生事件时调用
V17
在v17中的批量更新分成两种,同步和异步模式。同步模式和v15类似
function App() {
const [count, setCount] = useState(0)
const onClick = () => {
setCount(v => v + 1)
setCount(v => v + 1)
setCount(v => v + 1)
setCount(v => v + 1)
}
return (
<div className="App">
<p onClick={onClick} >{count}</p>
aapp
</div>
);
}
Sync
function batchedUpdates$1(fn, a) {
var prevExecutionContext = executionContext;
executionContext |= BatchedContext; // 增加批量更新的上下文
try {
// 执行事件中的回调,也就是dispatchEvent
return fn(a);
} finally {
executionContext = prevExecutionContext; // 回滚
if (executionContext === NoContext &&
!( ReactCurrentActQueue$1.isBatchingLegacy)) { // 执行批量更新
resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode();
}
}
}
在其中fn执行是同步执行,也就是事件中异步的调用更新不具有批量更新的优化
Conurrent
比如click事件中触发的更新,属于高优先级更新,执行派发事件时会通过runWithPriority
来执行,scheduler内部会保存这个提供的优先级。在函数内部所获取到的就是依然是同一个优先级
// 该函数回调执行中获取的优先级就是,执行回调传入的优先级
function getCurrentUpdatePriority() {
return currentUpdatePriority;
}
// 每次开始任务前都会执行返回获取一个当前的lane
function requestUpdateLane(fiber) {
// 获取当前lane, 也就是currentUpdatePriority,
// 这个变量在执行之前已经被赋值过了也就是事件中的优先级
var updateLane = getCurrentUpdatePriority();
// 如果update可用,就返回,在一次click事件处理函数中,总是会走到这里返回
if (updateLane !== NoLane) {
return updateLane;
}
var eventLane = getCurrentEventPriority();
return eventLane;
}
// 也就是同一个执行上下文里面获取的lane相同,会被执行批量更新
事件派发在react中被统一管理,触发事件时会在fiber链表中执行对应的回调函数来模拟事件冒泡
但是在派发事件前会做一部分工作
// 在派发一次事件时
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
// 获取当前的优先级 为0也就是NoLane
var previousPriority = getCurrentUpdatePriority();
// ...
try {
// 设置react的优先级会改写currentUpdatePriority变量
setCurrentUpdatePriority(DiscreteEventPriority);
// 派发事件
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
// 回滚
setCurrentUpdatePriority(previousPriority);
}
}
走到这里,一次事件中的优先级是同样的,所以后面的setState获取的优先级就是现在给的
function ensureRootIsScheduled(root, currentTime) {
var existingCallbackNode = root.callbackNode;
// 获取 `react` 当前优先级最高的lanes, 也就是当前上下文中可以使用的lanes
var nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
// ...
// 使用最高优先级 置换 scheduler中的优先级
var newCallbackPriority = getHighestPriorityLane(nextLanes);
var existingCallbackPriority = root.callbackPriority;
// 当前新任务和正在执行的任务优先级相同就返回
// 优先级相同代表lane相同,lane相同代表所处的上下文相同
if (existingCallbackPriority === newCallbackPriority &&
!( ReactCurrentActQueue$1.current !== null &&
existingCallbackNode !== fakeActCallbackNode)
) {
return;
}
// 如果新任务的优先级高于正在执行任务的优先级,就取消旧任务,重新加入新任务的回调
if (existingCallbackNode != null) {
cancelCallback$1(existingCallbackNode);
}
var newCallbackNode; // scheduleSyncCallback 调度返回的任务,同步、异步、blocking模式
// ... 略
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
在异步模式下,基于lane模型的更新实现,在异步回调函数中的更新也可以得到批量更新
所以在执行时所在环境的优先级相同,最后得到的sheduler优先级相同,在开始调度时都只会执行第一次。
基于Lane的更新
基于lane中的更新是固定的,比如click就是userBlocking的优先级,同样的优先级回返回同样的lane
现在批量更新是处于同一个上下文中的更新会被合并,因为优先级一样,lane一样
如果是在更新中又发起的更新,可能会根据message
事件获取eventLane,比如在Transtion上下文中。这种就会被合并