js
export default function Index(){
const [ number , setNumber ] = useState(0)
/* 同步条件下 */
const handleClickSync = () => {
setNumber((num) => num + 1)
Promise.resolve().then(() => setNumber((num) => num + 1))
setNumber((num) => num + 1)
}
/* 异步条件下 */
const handleClick = () => {
setTimeout(()=>{
setNumber((num) => num + 1)
Promise.resolve().then(() => setNumber((num) => num + 1))
setNumber((num) => num + 1)
},0)
}
console.log('----组件渲染----')
return <div>
{number}
<button onClick={handleClickSync} >同步环境下</button>
<button onClick={handleClick} >异步环境下</button>
</div>
}
同步批量更新
首先在触发点击事件的时候,会将currentUpdatePriority = 2,这决定着lane优先级 = 2
js
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
var previousPriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
setCurrentUpdatePriority(DiscreteEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
之后执行点击的回调事件
第一次setState
js
if (!didScheduleMicrotask) {
didScheduleMicrotask = true;
scheduleImmediateTask(processRootScheduleInMicrotask);
}
第一次执行,将processRootScheduleInMicrotask推入微任务队列,并将didScheduleMicrotask = true
第二次setState
由于第一次执行的时候didScheduleMicrotask = true,所以第二次不会进行processRootScheduleInMicrotask
宏任务结束
现在微任务队列有[processRootScheduleInMicrotask]
第三次setState
第三次setState是异步的,微任务队列现在有两个任务[processRootScheduleInMicrotask, dispatch]
微任务执行
微任务执行首先把didScheduleMicrotask = false,标志着下一个setState也能进行processRootScheduleInMicrotask
由于lane = 2,导致mightHavePendingSyncWork = true,这个变量决定了会进入performSyncWorkOnRoot
js
function processRootScheduleInMicrotask() {
var nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
if (nextLanes === NoLane) {
// This root has no more pending work. Remove it from the schedule. To
// guard against subtle reentrancy bugs, this microtask is the only place
// we do this --- you can add roots to the schedule whenever, but you can
// only remove them here.
// Null this out so we know it's been removed from the schedule.
root.next = null;
if (prev === null) {
// This is the new head of the list
firstScheduledRoot = next;
} else {
prev.next = next;
}
if (next === null) {
// This is the new tail of the list
lastScheduledRoot = prev;
}
} else {
// This root still has work. Keep it in the list.
prev = root;
if (includesSyncLane(nextLanes)) {
mightHavePendingSyncWork = true;
}
}
//...
flushSyncWorkOnAllRoots();
}
之后就是processRootScheduleInMicrotask的逻辑
等到processRootScheduleInMicrotask执行完,处理下一个微任务,同样也会将processRootScheduleInMicrotask放入微任务队列
此时微任务队列有[processRootScheduleInMicrotask]
如果同时有注册了多个微任务,那么微任务队列此时是[processRootScheduleInMicrotask, dispatch,dispatch, ...]
processRootScheduleInMicrotask执行完毕,打开能processRootScheduleInMicrotask的开关,然后进行dispatch,dispatch后将processRootScheduleInMicrotask推入微任务,同时关闭processRootScheduleInMicrotask开关,后续的dispatch推不进processRootScheduleInMicrotask,因此同时有多个微任务也只能执行一次processRootScheduleInMicrotask
总结
同步批量更新下,多个同步setState只会触发一次processRootScheduleInMicrotask,微任务setState会再次触发processRootScheduleInMicrotask以及render
代码结果是进行了两次render
异步批量更新
setTimeout执行回调会在点击事件结束之后,此时currentEventPriority已经恢复,点击事件也已经结束,所以lane会取默认值32,后面几步和同步情况一样,共有两次processRootScheduleInMicrotask的微任务
js
var updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
} // This update originated outside React. Ask the host environment for an
// appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
var eventLane = getCurrentEventPriority();
return eventLane;
第一次setState
第一次执行,将processRootScheduleInMicrotask推入微任务队列,并将didScheduleMicrotask = true
第二次setState
由于第一次执行的时候didScheduleMicrotask = true,所以第二次不会进行processRootScheduleInMicrotask
宏任务结束
现在微任务队列有[processRootScheduleInMicrotask]
第三次setState
第三次setState是异步的,微任务队列现在有两个任务[processRootScheduleInMicrotask, dispatch]
到目前为止除了lane不同,其他和同步一样
微任务执行
js
funnction scheduleTaskForRootDuringMicrotask(root, currentTime) {
// ......
if (includesSyncLane(nextLanes)) {
// Synchronous work is always flushed at the end of the microtask, so we
// don't need to schedule an additional task.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackPriority = SyncLane;
root.callbackNode = null;
return SyncLane;
}
}
// ......
之前同步由于lane = 2,进行到这里就返回了,异步还会往下执行
js
else {
// We use the highest priority lane to represent the priority of the callback.
var existingCallbackPriority = root.callbackPriority;
var newCallbackPriority = getHighestPriorityLane(nextLanes);
if (newCallbackPriority === existingCallbackPriority && // Special case related to `act`. If the currently scheduled task is a
// Scheduler task, rather than an `act` task, cancel it and re-schedule
// on the `act` queue.
!(ReactCurrentActQueue$2.current !== null && existingCallbackNode !== fakeActCallbackNode$1)) {
// The priority hasn't changed. We can reuse the existing task.
return newCallbackPriority;
} else {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
}
var schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediatePriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalPriority$1;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdlePriority;
break;
default:
schedulerPriorityLevel = NormalPriority$1;
break;
}
var newCallbackNode = scheduleCallback$2(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
return newCallbackPriority;
}
首先会取消还没执行的调度,等之后新的调度一起执行
新的调度挂在在root fiber上
32的调度等级是3,超时时间是10s
js
if (includesSyncLane(nextLanes)) {
mightHavePendingSyncWork = true;
}
同步的时候mightHavePendingSyncWork = true,异步执行不到,这个变量控制着能否执行performSyncWorkOnRoot
,因此异步进不到重新渲染
此时微任务执行完成,队列还有一个[dispatch]
dispatch 将processRootScheduleInMicrotask推入微任务队列后执行
此时 root fiber上面挂载着刚刚新建的调度callbackNode
由于刚刚新建的调度callbackNode 和此次的调度 lane一样,因此保留上一次的调度
所以微任务触发的setState不会重新创建调度
更进一步说,即使是setTimeout触发的setState,只要优先级一样,并且挂载callbackNode的时间在上一个调度执行前,都只会存在一个render 调度
总结
异步批量更新下, setState已经不看是否进入processRootScheduleInMicrotask,而是采用了schedule,多个setState在schedule触发前都只会存在一个render任务(优先级相同)
代码结果是只进行一次render