React Fiber 并发更新
一、作用
二、完成排队的并发更新
export function finishQueueingConcurrentUpdates(): void {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
concurrentlyUpdatedLanes = NoLanes;
let i = 0;
while (i < endIndex) {
const fiber: Fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
const queue: ConcurrentQueue = concurrentQueues[i];
concurrentQueues[i++] = null;
const update: ConcurrentUpdate = concurrentQueues[i];
concurrentQueues[i++] = null;
const lane: Lane = concurrentQueues[i];
concurrentQueues[i++] = null;
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
// 这是第一次更新。创建一个循环列表。
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (lane !== NoLane) {
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}
三、获取并发更新的 Lanes
export function getConcurrentlyUpdatedLanes(): Lanes {
return concurrentlyUpdatedLanes;
}
四、入队并发钩子更新
export function enqueueConcurrentHookUpdate<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = queue as any;
const concurrentUpdate: ConcurrentUpdate = update as any;
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
五、排队并发钩子更新并尽早退出
备注
getWorkInProgressRoot()由 ReactFiberWorkLoop 实现
export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
): void {
// This function is used to queue an update that doesn't need a rerender. The
// only reason we queue it is in case there's a subsequent higher priority
// update that causes it to be rebased.
//
// 这个函数用于排队一个不需要重新渲染的更新。
// 我们排队它的唯一原因是,以防之后有一个优先级更高的更新导致它被重置。
const lane = NoLane;
const concurrentQueue: ConcurrentQueue = queue as any;
const concurrentUpdate: ConcurrentUpdate = update as any;
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
// Usually we can rely on the upcoming render phase to process the concurrent
// queue. However, since this is a bail out, we're not scheduling any work
// here. So the update we just queued will leak until something else happens
// to schedule work (if ever).
//
// 通常我们可以依赖即将到来的渲染阶段来处理并发队列。
// 但是,由于这是一个中止,我们这里没有安排任何工作。
// 所以我们刚刚排队的更新将会泄漏,直到有其他事情发生来安排工作(如果有的话)。
//
// Check if we're currently in the middle of rendering a tree, and if not,
// process the queue immediately to prevent a leak.
//
// 检查我们当前是否正在渲染树,如果没有,
// 立即处理队列以防止内存泄漏。
const isConcurrentlyRendering = getWorkInProgressRoot() !== null;
if (!isConcurrentlyRendering) {
finishQueueingConcurrentUpdates();
}
}
六、排队并发类更新
export function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = queue as any;
const concurrentUpdate: ConcurrentUpdate = update as any;
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
七、为车道排队并发渲染
export function enqueueConcurrentRenderForLane(
fiber: Fiber,
lane: Lane,
): FiberRoot | null {
enqueueUpdate(fiber, null, null, lane);
return getRootForUpdatedFiber(fiber);
}
八、将更新标记从 Fiber 传递到根(不安全)
// Calling this function outside this module should only be done for backwards
// compatibility and should always be accompanied by a warning.
//
// 在此模块外调用此函数应仅出于向后兼容的目的,并且应始终伴随警告。
export function unsafe_markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
lane: Lane,
): FiberRoot | null {
// NOTE: For Hyrum's Law reasons, if an infinite update loop is detected, it
// should throw before `markUpdateLaneFromFiberToRoot` is called. But this is
// undefined behavior and we can change it if we need to; it just so happens
// that, at the time of this writing, there's an internal product test that
// happens to rely on this.
//
// 注意:出于 Hyrum 定律的原因,如果检测到无限更新循环,应该在调
// 用 `markUpdateLaneFromFiberToRoot` 之前抛出异常。但这是未定义行为,如果需要,我们
// 可以更改它;仅巧合的是,在撰写本文时,有一个内部产品测试恰好依赖于此。
const root = getRootForUpdatedFiber(sourceFiber);
markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
return root;
}
常量
1. 并发队列
// If a render is in progress, and we receive an update from a concurrent event,
// we wait until the current render is over (either finished or interrupted)
// before adding it to the fiber/hook queue. Push to this array so we can
// access the queue, fiber, update, et al later.
//
// 如果渲染正在进行中,并且我们收到了来自并发事件的更新,
// 我们会等到当前渲染结束(无论是完成还是被中断)
// 再将其添加到 fiber/hook 队列中。推入这个数组,这样我们
// 以后可以访问队列、fiber、更新等。
const concurrentQueues: Array<any> = [];
变量
1. 并发列队索引
let concurrentQueuesIndex = 0;
2. 同时更新的 Lanes
let concurrentlyUpdatedLanes: Lanes = NoLanes;
工具
1. 将更新标记从 Fiber 传递到根
备注
mergeLanes()由 ReactFiberLane#mergeLanes 实现markHiddenUpdate()由 ReactFiberLane#markHiddenUpdate 实现
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
update: ConcurrentUpdate | null,
lane: Lane,
): null | FiberRoot {
// Update the source fiber's lanes
// 更新源 fiber 的车道
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
// Walk the parent path to the root and update the child lanes.
// 沿着父路径走到根节点,并更新子车道。
let isHidden = false;
let parent = sourceFiber.return;
let node = sourceFiber;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
if (parent.tag === OffscreenComponent) {
// Check if this offscreen boundary is currently hidden.
// 检查此屏幕外边界当前是否隐藏。
//
// The instance may be null if the Offscreen parent was unmounted. Usually
// the parent wouldn't be reachable in that case because we disconnect
// fibers from the tree when they are deleted. However, there's a weird
// edge case where setState is called on a fiber that was interrupted
// before it ever mounted. Because it never mounts, it also never gets
// deleted. Because it never gets deleted, its return pointer never gets
// disconnected. Which means it may be attached to a deleted Offscreen
// parent node. (This discovery suggests it may be better for memory usage
// if we don't attach the `return` pointer until the commit phase, though
// in order to do that we'd need some other way to track the return
// pointer during the initial render, like on the stack.)
//
// 如果离屏(Offscreen)父节点被卸载,实例可能为 null。通常情况下,
// 父节点在这种情况下是不可访问的,因为我们在删除 fiber 时会将它们从树中断开连接。
// 但是,有一个奇怪的边缘情况:在某个 fiber 在挂载前被中断时调用了 setState。
// 因为它从未挂载,它也从未被删除。因为它从未被删除,它的 return 指针也从未被断开连接。
// 这意味着它可能附着在一个已删除的离屏父节点上。
// (这一发现表明,如果我们直到提交阶段才附加 `return` 指针,可能会更节省内存,
// 不过为了做到这一点,我们需要在初始渲染期间用其他方式跟踪 return 指针,比如在栈上。)
//
// This case is always accompanied by a warning, but we still need to
// account for it. (There may be other cases that we haven't discovered,
// too.)
//
// 这种情况总是伴随着警告,但我们仍然需要考虑到它。(可能还有其他我们尚未发现的情况。)
const offscreenInstance: OffscreenInstance | null = parent.stateNode;
if (
offscreenInstance !== null &&
!(offscreenInstance._visibility & OffscreenVisible)
) {
isHidden = true;
}
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
if (isHidden && update !== null) {
markHiddenUpdate(root, update, lane);
}
return root;
}
return null;
}
2. 入队更新
备注
mergeLanes()由 ReactFiberLane#mergeLanes 实现
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane,
) {
// Don't update the `childLanes` on the return path yet. If we already in
// the middle of rendering, wait until after it has completed.
//
// 暂时不要在返回路径上更新 `childLanes`。如果我们已经在渲染过程中,等到
// 渲染完成后再更新。
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
// The fiber's `lane` field is used in some places to check if any work is
// scheduled, to perform an eager bailout, so we need to update it immediately.
// TODO: We should probably move this to the "shared" queue instead.
//
// 纤程的 `lane` 字段在某些地方用于检查是否有任何工作被安排,以便进行快速退出,因此我们
// 需要立即更新它。
// TODO: 我们可能应该把它移动到“共享”队列中。
fiber.lanes = mergeLanes(fiber.lanes, lane);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
3. 获取更新 Fiber 的根
备注
throwIfInfiniteUpdateLoopDetected()由 ReactFiberWorkLoop 提供
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
// TODO: We will detect and infinite update loop and throw even if this fiber
// has already unmounted. This isn't really necessary but it happens to be the
// current behavior we've used for several release cycles. Consider not
// performing this check if the updated fiber already unmounted, since it's
// not possible for that to cause an infinite update loop.
//
// TODO: 我们将检测无限更新循环并抛出异常,即使这个 fiber 已经卸载。这实际上不是
// 必须的,但它恰好是我们在几个版本周期中使用的当前行为。如果更新的 fiber 已经卸载,考虑
// 不执行此检查,因为那不可能导致无限更新循环。
throwIfInfiniteUpdateLoopDetected();
// When a setState happens, we must ensure the root is scheduled. Because
// update queues do not have a backpointer to the root, the only way to do
// this currently is to walk up the return path. This used to not be a big
// deal because we would have to walk up the return path to set
// the `childLanes`, anyway, but now those two traversals happen at
// different times.
// TODO: Consider adding a `root` backpointer on the update queue.
//
// 当 setState 发生时,我们必须确保根节点被调度。因为
// 更新队列没有指向根节点的反向指针,目前唯一的方法是沿着返回路径向上遍历。以前这不是大问题,
// 因为我们无论如何都必须沿返回路径向上遍历以设置 `childLanes`,但现在
// 这两次遍历发生在不同的时间。
// TODO: 考虑在更新队列上添加 `root` 反向指针。
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
detectUpdateOnUnmountedFiber(sourceFiber, node);
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? (node.stateNode as FiberRoot) : null;
}
4. detectUpdateOnUnmountedFiber
备注
warnAboutUpdateOnNotYetMountedFiberInDEV()由 ReactFiberWorkLoop 提供
function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
if (__DEV__) {
const alternate = parent.alternate;
if (
alternate === null &&
(parent.flags & (Placement | Hydrating)) !== NoFlags
) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}