批量更新效果展示:
const App = () => {
const [number, setNumber] = React.useState(1);
console.log("App 组件, 渲染");
const handleClick = () => {
setNumber((pre) => (pre += 1));
setNumber((pre) => (pre += 1));
setNumber((pre) => (pre += 1));
};
return (
<div>
{number}
<div
onClick={handleClick}
>
批量更新
</div>
</div>
);
};
我们发现,触发三次 setNumber
, App 组件 只渲染一次,并且 number
值可以更新为 4。
批量更新原理
第一:当点击的时候, 触发第一次 setNumber, 调用的是 dispatchSetState
, 在 该函数中,将 update
对象 加入到 hook.queue.pending
循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber
函数,从根节点开始调度更新。
const dispatchSetState = () => {
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
第二:scheduleUpdateOnFiber
函数 中 调用了 ensureRootIsScheduled
函数。
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
eventTime: number
) {
ensureRootIsScheduled 函数。(root, eventTime);
}
第三: ensureRootIsScheduled
函数中,将 performConcurrentWorkOnRoot
交给 Scheduler
, 进行异步调用。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
第四:异步调用之后,又同步来到了 第二个 setNumber 函数调用。调用的是 dispatchSetState
, 在 该函数中,将 update
对象 加入到 hook.queue.pending
循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber
函数,从根节点开始调度更新。
const dispatchSetState = () => {
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
第五:scheduleUpdateOnFiber
函数 中 调用了 ensureRootIsScheduled
函数。
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
eventTime: number
) {
ensureRootIsScheduled 函数。(root, eventTime);
}
第六: ensureRootIsScheduled
函数中,判断新的更新优先级 和 当前已经存在的优先级相等,则不会进行调度更新,进行返回。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
if (existingCallbackPriority === newCallbackPriority) {
return;
}
}
第七:异步调用之后,又同步来到了 第三个 setNumber 函数调用。调用的是 dispatchSetState
, 在 该函数中,将 update
对象 加入到 hook.queue.pending
循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber
函数,从根节点开始调度更新。
const dispatchSetState = () => {
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
第八:scheduleUpdateOnFiber
函数 中 调用了 ensureRootIsScheduled
函数。
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
eventTime: number
) {
ensureRootIsScheduled 函数。(root, eventTime);
}
第九: ensureRootIsScheduled
函数中,判断新的更新优先级 和 当前已经存在的优先级相等,则不会进行调度更新,进行返回。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
if (existingCallbackPriority === newCallbackPriority) {
return;
}
}
第十:三次 setNumber()
同步代码执行完毕后,空闲时间时,开始执行由第一次更新产生的调度任务,从根节点开始,深度优先调度 fiber 节点,进行更新。更新到 App 组件时,调用 useState, 更新时调用的 useState, 是 updateReducer
函数,在 updateReducer
函数中,根据三次更新产生的更新队列,计算出新状态 4 -> 提交。
自此 批量更新结束。