React Fiber 过渡
一、作用
二、导出的常量
1. 没有过渡
export const NoTransition = null;
三、请求当前过渡
export function requestCurrentTransition(): Transition | null {
return ReactSharedInternals.T;
}
四、从池中请求缓存
备注
getWorkInProgressRoot()在 ReactFiberWorkLoop 中实现createCache()由 ReactFiberCacheComponent#createCache 实现retainCache()由 ReactFiberCacheComponent#retainCache 实现
export function requestCacheFromPool(renderLanes: Lanes): Cache {
// Similar to previous function, except if there's not already a cache in the
// pool, we allocate a new one.
//
// 类似于之前的函数,只是如果池中还没有缓存,我们就分配一个新的。
const cacheFromPool = peekCacheFromPool(); // 本文档实现
if (cacheFromPool !== null) {
return cacheFromPool;
}
// Create a fresh cache and add it to the root cache pool. A cache can have
// multiple owners:
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
// are originally created (TODO: except during refreshes, until we implement
// this correctly). The root takes ownership immediately when the cache is
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
// and the return value of this function is a &Arc<Cache> (borrowed).
// - One of several fiber types: host root, cache boundary, suspense
// component. These retain and release in the commit phase.
//
// 创建一个新的缓存并将其添加到根缓存池中。一个缓存可以有多个所有者:
// - 存放在 FiberRoot 上的缓存池。所有新的缓存最初都是在这里创建的(TODO:
// 刷新期间除外,直到我们正确实现此功能)。当缓存创建时,根会立即获得所有权。
// 概念上,root.pooledCache 是一个 Option<Arc<Cache>>(拥有所有权),而
// 此函数的返回值是 &Arc<Cache>(借用)。
// - 若干 Fiber 类型之一:主机根、缓存边界、Suspense 组件。这些在提交阶段保留和释放。
const root = getWorkInProgressRoot() as any;
const freshCache = createCache();
root.pooledCache = freshCache;
retainCache(freshCache);
if (freshCache !== null) {
root.pooledCacheLanes |= renderLanes;
}
return freshCache;
}
五、推送根过渡
备注
getWorkInProgressTransitions()在 ReactFiberWorkLoop 中实现push()由 ReactFiberStack#push 实现
export function pushRootTransition(
workInProgress: Fiber,
root: FiberRoot,
renderLanes: Lanes,
) {
if (enableTransitionTracing) {
const rootTransitions = getWorkInProgressTransitions();
push(transitionStack, rootTransitions, workInProgress);
}
}
六、弹出根过渡
备注
pop()由 ReactFiberStack#pop 实现
export function popRootTransition(
workInProgress: Fiber,
root: FiberRoot,
renderLanes: Lanes,
) {
if (enableTransitionTracing) {
pop(transitionStack, workInProgress);
}
}
七、推送过渡
备注
push()由 ReactFiberStack#push 实现
export function pushTransition(
offscreenWorkInProgress: Fiber,
prevCachePool: SpawnedCachePool | null,
newTransitions: Array<Transition> | null,
): void {
if (prevCachePool === null) {
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
} else {
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
}
if (enableTransitionTracing) {
if (transitionStack.current === null) {
push(transitionStack, newTransitions, offscreenWorkInProgress);
} else if (newTransitions === null) {
push(transitionStack, transitionStack.current, offscreenWorkInProgress);
} else {
push(
transitionStack,
transitionStack.current.concat(newTransitions),
offscreenWorkInProgress,
);
}
}
}
八、弹出过渡
备注
pop()由 ReactFiberStack#pop 实现
export function popTransition(workInProgress: Fiber, current: Fiber | null) {
if (current !== null) {
if (enableTransitionTracing) {
pop(transitionStack, workInProgress);
}
pop(resumedCache, workInProgress);
}
}
九、获取待处理的过渡
export function getPendingTransitions(): Array<Transition> | null {
if (!enableTransitionTracing) {
return null;
}
return transitionStack.current;
}
十、获取挂起缓存
export function getSuspendedCache(): SpawnedCachePool | null {
// This function is called when a Suspense boundary suspends. It returns the
// cache that would have been used to render fresh data during this render,
// if there was any, so that we can resume rendering with the same cache when
// we receive more data.
//
// 当 Suspense 边界挂起时,会调用此函数。它返回在此渲染期间用于渲染新数据的
// 缓存(如果有的话),这样当我们收到更多数据时,就可以使用相同的缓存继续渲染。
const cacheFromPool = peekCacheFromPool(); // 本文档实现
if (cacheFromPool === null) {
return null;
}
return {
// We must also save the parent, so that when we resume we can detect
// a refresh.
//
// 我们还必须保存父对象,这样当我们恢复时可以检测到刷新。
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}
十一、获取离屏延迟缓存
export function getOffscreenDeferredCache(): SpawnedCachePool | null {
const cacheFromPool = peekCacheFromPool(); // 本文档实现
if (cacheFromPool === null) {
return null;
}
return {
// We must also store the parent, so that when we resume we can detect
// a refresh.
//
// 我们还必须存储父项,这样在恢复时我们才能检测到刷新。
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}
十二、常量
1. 上次开始过渡完成时
信息
markTransitionStarted()由 ReactFiberWorkLoop 中实现startAsyncTransitionTimer()由 ReactProfilerTimer@startAsyncTransitionTimer 实现entangleAsyncAction()由 ReactFiberAsyncAction#entangleAsyncAction 实现queueTransitionTypes()由 ReactFiberTransitionTypes#queueTransitionTypes 实现entangleAsyncTransitionTypes()由 ReactFiberTransitionTypes#entangleAsyncTransitionTypes 实现peekEntangledActionLane()由 ReactFiberAsyncAction#peekEntangledActionLane 实现
// Attach this reconciler instance's onStartTransitionFinish implementation to
// the shared internals object. This is used by the isomorphic implementation of
// startTransition to compose all the startTransitions together.
//
// 将此调和器实例的 onStartTransitionFinish 实现附加到
// 共享内部对象上。这个方法被同构实现的
// startTransition 用来将所有的 startTransition 组合在一起。
//
// function startTransition(fn) {
// return startTransitionDOM(() => {
// return startTransitionART(() => {
// return startTransitionThreeFiber(() => {
// // and so on...
// return fn();
// });
// });
// });
// }
//
// Currently we only compose together the code that runs at the end of each
// startTransition, because for now that's sufficient — the part that sets
// isTransition=true on the stack uses a separate shared internal field. But
// really we should delete the shared field and track isTransition per
// reconciler. Leaving this for a future PR.
//
// 目前我们只组合在每次 startTransition 结束时运行的代码,因为目前这样就
// 足够了 —— 设置堆栈上 isTransition=true 的部分使用了一个单独的共享内部字段。
// 但实际上我们应该删除这个共享字段,并针对每个协调器跟踪 isTransition。
// 此部分留作未来的 PR。
const prevOnStartTransitionFinish = ReactSharedInternals.S;
ReactSharedInternals.S = function onStartTransitionFinishForReconciler(
transition: Transition,
returnValue: mixed,
) {
markTransitionStarted(); // 标记状态
if (
typeof returnValue === 'object' &&
returnValue !== null &&
typeof returnValue.then === 'function'
) {
// If we're going to wait on some async work before scheduling an update.
// We mark the time so we can later log how long we were blocked on the Action.
// Ideally, we'd include the sync part of the action too but since that starts
// in isomorphic code it currently leads to tricky layering. We'd have to pass
// in performance.now() to this callback but we sometimes use a polyfill.
//
// 如果我们打算在安排更新之前等待一些异步工作。
// 我们会记录时间,这样以后可以记录我们在该动作上被阻塞了多久。
// 理想情况下,我们也会包含动作的同步部分,但由于它在同构代码中启动,当前会导致层次结构复杂。
// 我们必须将 performance.now() 传入此回调,但有时我们会使用填充程序。
startAsyncTransitionTimer();
// This is an async action
// 这是一个异步操作
const thenable: Thenable<mixed> = returnValue as any;
entangleAsyncAction(transition, thenable);
}
if (enableViewTransition) {
if (entangledTransitionTypes !== null) {
// If we scheduled work on any new roots, we need to add any entangled async
// transition types to those roots too.
//
// 如果我们安排了对任何新根的工作,我们也需要将任何相关的异步
// 转换类型添加到这些根中。
let root = firstScheduledRoot;
while (root !== null) {
queueTransitionTypes(root, entangledTransitionTypes);
root = root.next;
}
}
const transitionTypes = transition.types;
if (transitionTypes !== null) {
// Within this Transition we should've now scheduled any roots we have updates
// to work on. If there are no updates on a root, then the Transition type won't
// be applied to that root.
//
// 在此过渡期间,我们现在应该已经安排好要更新的所有根节点。
// 如果某个根节点没有更新,那么该过渡类型将不会应用到该根节点。
let root = firstScheduledRoot;
while (root !== null) {
queueTransitionTypes(root, transitionTypes);
root = root.next;
}
if (peekEntangledActionLane() !== NoLane) {
// If we have entangled, async actions going on, the update associated with
// these types might come later. We need to save them for later.
//
// 如果我们有纠缠的异步操作在进行,与这些类型相关的更新可能会稍后才出现。
// 我们需要把它们保存起来以备后用。
entangleAsyncTransitionTypes(transitionTypes);
}
}
}
if (prevOnStartTransitionFinish !== null) {
prevOnStartTransitionFinish(transition, returnValue);
}
};
2. 恢复的缓存
备注
createCursor()由 ReactFiberStack#createCursor 实现
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
//
// 在重试 Suspense/Offscreen 边界时,我们通过
// 将之前渲染期间使用的缓存放在这里(栈上)来恢复它。
const resumedCache: StackCursor<Cache | null> = createCursor(null);
3. 过渡堆栈
备注
createCursor()由 ReactFiberStack#createCursor 实现
// During the render/synchronous commit phase, we don't actually process the
// transitions. Therefore, we want to lazily combine transitions. Instead of
// comparing the arrays of transitions when we combine them and storing them
// and filtering out the duplicates, we will instead store the unprocessed transitions
// in an array and actually filter them in the passive phase.
//
// 在渲染/同步提交阶段,我们实际上并不会处理这些过渡。因此,我们
// 希望延迟合并过渡。我们不会在合并时比较过渡数组并存储它们然后过滤重复项,而是将未处理的
// 过渡存储在一个数组中,并在被动阶段实际进行过滤。
const transitionStack: StackCursor<Array<Transition> | null> =
createCursor(null);
4. 恢复的缓存
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
// 在重试 Suspense/Offscreen 边界时,我们通过将之前
// 渲染期间使用的缓存放在这里(栈上)来恢复它。
const resumedCache: StackCursor<Cache | null> = createCursor(null);
十三、工具
1. 链式手势取消
信息
备注
cancelScheduledGesture()由 ReactFiberGestureScheduler#cancelScheduledGesture 实现
代码中有一段自执行逻辑调用该方法
:::
function chainGestureCancellation(
root: FiberRoot,
scheduledGesture: ScheduledGesture,
prevCancel: null | (() => void),
): () => void {
return function cancelGesture(): void {
if (scheduledGesture !== null) {
cancelScheduledGesture(root, scheduledGesture);
}
if (prevCancel !== null) {
prevCancel();
}
};
}
2. 在协调器上开始手势过渡完成时
信息
该逻辑是该文档的一段自执行逻辑,在源码的 147 - 190 行
备注
startScheduledGesture()由 ReactFiberGestureScheduler#startScheduledGesture 实现
if (enableGestureTransition) {
const prevOnStartGestureTransitionFinish = ReactSharedInternals.G;
ReactSharedInternals.G = function onStartGestureTransitionFinishForReconciler(
transition: Transition,
provider: GestureProvider,
options: ?GestureOptions,
): () => void {
let cancel = null;
if (prevOnStartGestureTransitionFinish !== null) {
cancel = prevOnStartGestureTransitionFinish(
transition,
provider,
options,
);
}
// For every root that has work scheduled, check if there's a ScheduledGesture
// matching this provider and if so, increase its ref count so its retained by
// this cancellation callback. We could add the roots to a temporary array as
// we schedule them inside the callback to keep track of them. There's a slight
// nuance here which is that if there's more than one root scheduled with the
// same provider, but it doesn't update in this callback, then we still update
// its options and retain it until this cancellation releases. The idea being
// that it's conceptually started globally.
//
// 对于每个已安排工作的根节点,检查是否有与该提供者匹配的 ScheduledGesture。
// 如果有,则增加其引用计数,以便该取消回调保留它。我们可以在回调中安排它们时,将
// 根节点添加到一个临时数组中以跟踪它们。这里有一个细微的区别,即如果有多个根节点
// 使用相同的提供者被安排,但在此回调中没有更新,则我们仍然更新其选项并保留它,直到
// 此取消操作释放它。其想法是从概念上讲,它已经在全局范围内启动了。
let root = firstScheduledRoot;
while (root !== null) {
const scheduledGesture = startScheduledGesture(
root,
provider,
options,
transition.types,
);
if (scheduledGesture !== null) {
cancel = chainGestureCancellation(root, scheduledGesture, cancel);
}
root = root.next;
}
if (cancel !== null) {
return cancel;
}
return function cancelGesture(): void {
// Nothing was scheduled but it could've been scheduled by another renderer.
// 没有安排任何内容,但可能已被其他渲染器安排。
};
};
}
3. 从池中查看缓存
备注
getWorkInProgressRoot()在 ReactFiberWorkLoop 中实现
function peekCacheFromPool(): Cache | null {
// Check if the cache pool already has a cache we can use.
// 检查缓存池是否已有可用的缓存。
// If we're rendering inside a Suspense boundary that is currently hidden,
// we should use the same cache that we used during the previous render, if
// one exists.
//
// 如果我们在当前被隐藏的 Suspense 边界内进行渲染,
// 我们应该使用上一次渲染时使用的相同缓存(如果存在的话)。
// 从先前渲染恢复的缓存
const cacheResumedFromPreviousRender = resumedCache.current;
if (cacheResumedFromPreviousRender !== null) {
return cacheResumedFromPreviousRender;
}
// Otherwise, check the root's cache pool.
// 否则,检查根的缓存池。
const root = (getWorkInProgressRoot(): any);
const cacheFromRootCachePool = root.pooledCache;
return cacheFromRootCachePool;
}