React Fiber 应用手势
一、作用
二、插入目标克隆
备注
cancelRootViewTransitionName()由宿主环境提供
// Clone View Transition boundaries that have any mutations or might have had their
// layout affected by child insertions.
//
// 克隆视图过渡边界,这些边界有任何变更或可能因子元素插入而影响布局。
export function insertDestinationClones(
root: FiberRoot,
finishedWork: Fiber,
): void {
// We'll either not transition the root, or we'll transition the clone. Regardless
// we cancel the root view transition name.
//
// 我们要么不转换根节点,要么转换克隆节点。无论如何我们都会取消根视图的转换名称。
const needsClone = detectMutationOrInsertClones(finishedWork);
if (needsClone) {
if (__DEV__) {
if (!didWarnForRootClone) {
didWarnForRootClone = true;
console.warn(
'startGestureTransition() caused something to mutate or relayout the root. ' +
'This currently requires a clone of the whole document. Make sure to ' +
'add a <ViewTransition> directly around an absolutely positioned DOM node ' +
'to minimize the impact of any changes caused by the Gesture Transition.',
);
}
}
// Clone the whole root
// 克隆整个根
const rootClone = cloneRootViewTransitionContainer(root.containerInfo);
root.gestureClone = rootClone;
recursivelyInsertClones(finishedWork, rootClone, null, CLONE_UPDATE);
} else {
root.gestureClone = null;
cancelRootViewTransitionName(root.containerInfo);
}
}
三、应用离场过渡
备注
pushViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#pushViewTransitionCancelableScope 实现removeRootViewTransitionClone()由宿主环境提供cancelViewTransitionName()由宿主环境提供restoreRootViewTransitionName()由宿主环境提供cancelRootViewTransitionName()由宿主环境提供popViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#popViewTransitionCancelableScope 实现
// Revert insertions and apply view transition names to the "new" (current) state.
// 撤销插入操作并将视图过渡名称应用到“新”(当前)状态。
export function applyDepartureTransitions(
root: FiberRoot,
finishedWork: Fiber,
): void {
// First measure and apply view-transition-names to the "new" states.
// 首先测量并将 view-transition-names 应用于“新”状态。
viewTransitionContextChanged = false;
pushViewTransitionCancelableScope();
recursivelyApplyViewTransitions(finishedWork);
// Then remove the clones.
// 然后移除克隆。
const rootClone = root.gestureClone;
if (rootClone !== null) {
root.gestureClone = null;
removeRootViewTransitionClone(root.containerInfo, rootClone);
}
if (!viewTransitionContextChanged) {
// If we didn't leak any resizing out to the root, we don't have to transition
// the root itself. This means that we can now safely cancel any cancellations
// that bubbled all the way up.
//
// 如果我们没有将任何尺寸调整泄露到根节点,就不必对根节点本身进行过渡。这意味着我们现在可以
// 安全地取消所有冒泡到最顶层的取消操作。
const cancelableChildren = viewTransitionCancelableChildren;
if (cancelableChildren !== null) {
for (let i = 0; i < cancelableChildren.length; i += 3) {
cancelViewTransitionName(
cancelableChildren[i] as any as Instance,
cancelableChildren[i + 1] as any as string,
cancelableChildren[i + 2] as any as Props,
);
}
}
// We also cancel the root itself. First we restore the name to the documentElement
// and then we cancel it.
// 我们也会取消根本身。首先我们将名称恢复到 documentElement,然后再取消它。
restoreRootViewTransitionName(root.containerInfo);
cancelRootViewTransitionName(root.containerInfo);
}
popViewTransitionCancelableScope(null);
}
四、开始手势动画
备注
restoreRootViewTransitionName()由宿主环境提供
// Revert transition names and start/adjust animations on the started View Transition.
// 恢复过渡名称,并在已启动的视图过渡上启动/调整动画。
export function startGestureAnimations(
root: FiberRoot,
finishedWork: Fiber,
): void {
restoreViewTransitionsOnFiber(finishedWork);
restoreRootViewTransitionName(root.containerInfo);
}
五、常量
// Mutations in this subtree or potentially affected by layout.
// 此子树中的变更或可能受布局影响。
// 克隆:更新
const CLONE_UPDATE = 0;
// Inside a reappearing offscreen before the next ViewTransition or HostComponent.
// 在下一次 ViewTransition 或 HostComponent 之前,在重新出现的屏幕外内部。
// 克隆:退出
const CLONE_EXIT = 1;
// Inside a reappearing offscreen before the next HostComponent.
// 在下一个 HostComponent 之前,在屏幕外重新出现的内部。
// 克隆:取消隐藏
const CLONE_UNHIDE = 2;
// Like UNHIDE but we're already inside the first Host Component only finding pairs.
// 像 UNHIDE,但我们已经在第一个宿主环境组件内部,只是寻找配对项。
// 克隆:出现的配对
const CLONE_APPEARING_PAIR = 3;
// Nothing in this tree was changed but we're still walking to clone it.
// 这个树里的内容没有被修改,但我们仍在遍历以克隆它。
// 克隆:未更改
const CLONE_UNCHANGED = 4;
// Inside a newly mounted tree before the next ViewTransition or HostComponent.
// 在下一个视图转换或宿主组件之前,新挂载的树内部。
// 插入:退出
const INSERT_EXIT = 5;
// Inside a newly mounted tree before the next HostComponent.
// 在下一个宿主组件之前的新挂载树内部。
// 插入:追加
const INSERT_APPEND = 6;
// Inside a newly mounted tree only finding pairs.
// 在新挂载的树中只找到配对项。
// 插入:出现的配对
const INSERT_APPEARING_PAIR = 7;
六、变量
// 已警告根克隆
let didWarnForRootClone = false;
七、工具
1. 检测突变或插入克隆
function detectMutationOrInsertClones(finishedWork: Fiber): boolean {
return true;
}
2. 将视图过渡应用于克隆
备注
applyViewTransitionName()由宿主环境提供trackAnimatingTask()由 ReactProfilerTimer#trackAnimatingTask 实现
function applyViewTransitionToClones(
name: string,
className: ?string,
clones: Array<Instance>,
fiber: Fiber,
): void {
// This gets called when we have found a pair, but after the clone in created. The clone is
// created by the insertion side. If the insertion side if found before the deletion side
// then this is called by the deletion. If the deletion is visited first then this is called
// later by the insertion when the clone has been created.
//
// 当我们找到一对对象时会调用此方法,但在克隆创建之后。克隆是由插入方创建的。
// 如果在删除方之前找到了插入方,那么此方法会由删除方调用。
// 如果先访问了删除方,那么当克隆被创建后,此方法会由插入方稍后调用。
for (let i = 0; i < clones.length; i++) {
applyViewTransitionName(
clones[i],
i === 0
? name
: // If we have multiple Host Instances below, we add a suffix to the name to give
// each one a unique name.
//
// 如果下面有多个宿主环境实例,我们会在名称后添加一个后缀以给每个实例一个唯一的名称。
name + '_' + i,
className,
);
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (fiber._debugTask != null) {
trackAnimatingTask(fiber._debugTask);
}
}
}
3. 跟踪已删除的成对视图转换
备注
getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function trackDeletedPairViewTransitions(deletion: Fiber): void {
if (
appearingViewTransitions === null ||
appearingViewTransitions.size === 0
) {
// We've found all.
// 我们已经找到了全部。
return;
}
const pairs = appearingViewTransitions;
if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
// This has no named view transitions in its subtree.
// 其子树中没有命名的视图过渡。
return;
}
let child = deletion.child;
while (child !== null) {
if (child.tag === OffscreenComponent && child.memoizedState !== null) {
// This tree was already hidden so we skip it.
// 这棵树已经被隐藏,所以我们跳过它。
} else {
if (
child.tag === ViewTransitionComponent &&
(child.flags & ViewTransitionNamedStatic) !== NoFlags
) {
const props: ViewTransitionProps = child.memoizedProps;
const name = props.name;
if (name != null && name !== 'auto') {
const pair = pairs.get(name);
if (pair !== undefined) {
// Delete the entry so that we know when we've found all of them
// and can stop searching (size reaches zero).
//
// 删除该条目,这样我们就知道何时找到了所有条目并且可以停止搜索(大小为零)。
pairs.delete(name);
const className: ?string = getViewTransitionClassName(
props.default,
props.share,
);
if (className !== 'none') {
// TODO: Since the deleted instance already has layout we could
// check if it's in the viewport and if not skip the pairing.
// It would currently cause layout thrash though so if we did that
// we need to avoid inserting the root of the cloned trees until
// the end.
//
// 待办事项:由于被删除的实例已经有布局,我们可以检查它是否在视口内,如果不在则
// 跳过配对。不过目前这样会导致布局抖动,所以如果我们这么做就需要避免在结束前
// 插入克隆树的根节点。
// The "old" instance is actually the one we're inserting.
// “旧”实例实际上就是我们正在插入的那个实例。
const oldInstance: ViewTransitionState = pair;
// The "new" instance is the already mounted one we're deleting.
// “新”实例是我们正在删除的已挂载实例。
const newInstance: ViewTransitionState = child.stateNode;
oldInstance.paired = newInstance;
newInstance.paired = oldInstance;
const clones = oldInstance.clones;
if (clones !== null) {
// If we have clones that means that we've already visited this
// ViewTransition boundary before and we can now apply the name
// to those clones. Otherwise, we have to wait until we clone it.
//
// 如果我们有克隆,这意味着我们之前已经访问过这个
// ViewTransition 边界,现在我们可以将名称
// 应用于这些克隆。否则,我们必须等到克隆它时再处理。
applyViewTransitionToClones(name, className, clones, child);
}
}
if (pairs.size === 0) {
break;
}
}
}
}
trackDeletedPairViewTransitions(child);
}
child = child.sibling;
}
}
4. 跟踪进入视图的过渡
备注
getViewTransitionName()由 ReactFiberViewTransitionComponent#getViewTransitionName 实现getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function trackEnterViewTransitions(deletion: Fiber): void {
if (deletion.tag === ViewTransitionComponent) {
const props: ViewTransitionProps = deletion.memoizedProps;
const name = getViewTransitionName(props, deletion.stateNode);
const pair =
appearingViewTransitions !== null
? appearingViewTransitions.get(name)
: undefined;
const className: ?string = getViewTransitionClassName(
props.default,
pair !== undefined ? props.share : props.enter,
);
if (className !== 'none') {
if (pair !== undefined) {
// TODO: Since the deleted instance already has layout we could
// check if it's in the viewport and if not skip the pairing.
// It would currently cause layout thrash though so if we did that
// we need to avoid inserting the root of the cloned trees until
// the end.
//
// 待办事项:由于被删除的实例已经有布局,我们可以检查它是否在视口内,如果不在则
// 跳过配对。不过目前这样会导致布局抖动,所以如果我们这么做就需要避免在结束前
// 插入克隆树的根节点。
// Delete the entry so that we know when we've found all of them
// and can stop searching (size reaches zero).
//
// 删除该条目,以便我们知道何时找到了所有条目,并且可以停止搜索(大小达到零)。
appearingViewTransitions.delete(name);
// The "old" instance is actually the one we're inserting.
// “旧”实例实际上就是我们正在插入的那个。
const oldInstance: ViewTransitionState = pair;
// The "new" instance is the already mounted one we're deleting.
// “新”的实例是我们正在删除的已挂载实例。
const newInstance: ViewTransitionState = deletion.stateNode;
oldInstance.paired = newInstance;
newInstance.paired = oldInstance;
const clones = oldInstance.clones;
if (clones !== null) {
// If we have clones that means that we've already visited this
// ViewTransition boundary before and we can now apply the name
// to those clones. Otherwise, we have to wait until we clone it.
//
// 如果我们有克隆,这意味着我们之前已经访问过这个
// ViewTransition 边界,现在我们可以将名称
// 应用于这些克隆。否则,我们必须等到克隆它时再处理。
applyViewTransitionToClones(name, className, clones, deletion);
}
}
}
// Look for more pairs deeper in the tree.
// 在树的更深层查找更多的键值对。
trackDeletedPairViewTransitions(deletion);
} else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
let child = deletion.child;
while (child !== null) {
trackEnterViewTransitions(child);
child = child.sibling;
}
} else {
trackDeletedPairViewTransitions(deletion);
}
}
5. 应用出现成对视图过渡
备注
getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function applyAppearingPairViewTransition(child: Fiber): void {
// Normally these helpers do recursive calls but since insertion/offscreen is forked
// we call this helper from those loops instead. This must be called only on
// ViewTransitionComponent that has already had their clones filled.
//
// 通常这些辅助函数会进行递归调用,但由于插入/屏幕外操作是分叉的,我们在那些循环中调用这个辅助函数。
// 此函数只能在已经填充克隆的 ViewTransitionComponent 上调用。
if ((child.flags & ViewTransitionNamedStatic) !== NoFlags) {
const state: ViewTransitionState = child.stateNode;
// If this is not yet paired, it doesn't mean that we won't pair it later when
// we find the deletion side. If that's the case then we'll add the names to
// the clones then.
// 如果还没有配对,也不意味着我们以后不会配对它我们找到了删除侧。如果是这样,我们会把这些名字
// 加到那就克隆人吧。
if (state.paired) {
const props: ViewTransitionProps = child.memoizedProps;
if (props.name == null || props.name === 'auto') {
throw new Error(
'Found a pair with an auto name. This is a bug in React.',
);
}
const name = props.name;
// Note that this class name that doesn't actually really matter because the
// "new" side will be the one that wins in practice.
//
// 请注意,这个类名实际上并不重要,因为在实际中“new”那边才是最终起作用的。
const className: ?string = getViewTransitionClassName(
props.default,
props.share,
);
if (className !== 'none') {
const clones = state.clones;
// If there are no clones at this point, that should mean that there are no
// HostComponent children in this ViewTransition.
//
// 如果此时没有克隆,这应该意味着在此 ViewTransition 中没有 HostComponent 子项。
if (clones !== null) {
applyViewTransitionToClones(name, className, clones, child);
}
}
}
}
}
6. 应用退出视图过渡
备注
getViewTransitionName()由 ReactFiberViewTransitionComponent#getViewTransitionName 实现getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function applyExitViewTransition(placement: Fiber): void {
// Normally these helpers do recursive calls but since insertion/offscreen is forked
// we call this helper from those loops instead. This must be called only on
// ViewTransitionComponent that has already had their clones filled.
//
// 通常这些辅助函数会进行递归调用,但由于插入/屏幕外操作是分叉的
// 我们在那些循环中直接调用这个辅助函数。这个函数只能在
// 已经填充了克隆的 ViewTransitionComponent 上调用。
const state: ViewTransitionState = placement.stateNode;
const props: ViewTransitionProps = placement.memoizedProps;
const name = getViewTransitionName(props, state);
const className: ?string = getViewTransitionClassName(
props.default,
// Note that just because we don't have a pair yet doesn't mean we won't find one
// later. However, that doesn't matter because if we do the class name that wins
// is the one applied by the "new" side anyway.
//
// 注意,仅仅因为我们还没有找到一对,并不意味着我们以后找不到。
// 不过,这也无关紧要,因为即使找到了,最终生效的类名还是由“new”一方应用的那个。
state.paired ? props.share : props.exit,
);
if (className !== 'none') {
// TODO: Ideally we could determine if this exit is in the viewport and
// exclude it otherwise but that would require waiting until we insert
// and layout the clones first. Currently wait until the view transition
// starts before reading the layout.
//
// 待办:理想情况下,我们可以判断这个出口是否在视口中,否则就排除它,但那需要等到我们先
// 插入并布局克隆元素。目前是在视图过渡开始之前等待,然后再读取布局。
const clones = state.clones;
// If there are no clones at this point, that should mean that there are no
// HostComponent children in this ViewTransition.
//
// 如果此时没有克隆,这应该意味着在此 ViewTransition 中没有 HostComponent 子项。
if (clones !== null) {
applyViewTransitionToClones(name, className, clones, placement);
}
}
}
7. 应用嵌套视图过渡
备注
getViewTransitionName()由 ReactFiberViewTransitionComponent#getViewTransitionName 实现getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function applyNestedViewTransition(child: Fiber): void {
const state: ViewTransitionState = child.stateNode;
const props: ViewTransitionProps = child.memoizedProps;
const name = getViewTransitionName(props, state);
const className: ?string = getViewTransitionClassName(
props.default,
props.update,
);
if (className !== 'none') {
const clones = state.clones;
// If there are no clones at this point, that should mean that there are no
// HostComponent children in this ViewTransition.
//
// 如果此时没有克隆,这应该意味着在此 ViewTransition 中没有 HostComponent 子项。
if (clones !== null) {
applyViewTransitionToClones(name, className, clones, child);
}
}
}
8. 应用更新视图过渡
备注
getViewTransitionName()由 ReactFiberViewTransitionComponent#getViewTransitionName 实现getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现
function applyUpdateViewTransition(current: Fiber, finishedWork: Fiber): void {
const state: ViewTransitionState = finishedWork.stateNode;
// Updates can have conflicting names and classNames.
// Since we're doing a reverse animation the "new" state is actually the current
// and the "old" state is the finishedWork.
//
// 更新可能有冲突的名称和类名。
// 由于我们正在做反向动画,“新”状态实际上是当前状态,而“旧”状态是 finishedWork。
const newProps: ViewTransitionProps = current.memoizedProps;
const oldProps: ViewTransitionProps = finishedWork.memoizedProps;
const oldName = getViewTransitionName(oldProps, state);
// This className applies only if there are fewer child DOM nodes than
// before or if this update should've been cancelled but we ended up with
// a parent animating so we need to animate the child too. Otherwise
// the "new" state wins. Since "new" normally wins, that's usually what
// we would use. However, since this animation is going in reverse we actually
// want the props from "current" since that's the class that would've won if
// it was the normal direction. To preserve the same effect in either direction.
//
// 这个 className 仅在子 DOM 节点比以前少,或者本次更新本应被取消但最终父元素仍在动画中时
// 应用,所以我们也需要给子元素加动画。否则,“new”状态占优。由于“new”通常占优,这通常就是
// 我们会使用的方式。然而,由于这个动画是逆向的,我们实际上想要使用“current”的属性,因为如果
// 是正常方向,这个 class 本来会占优。以保证在任一方向上效果一致。
const className: ?string = getViewTransitionClassName(
newProps.default,
newProps.update,
);
if (className === 'none') {
// If update is "none" then we don't have to apply a name. Since we won't animate this boundary.
// 如果更新为“none”,那么我们不必应用名称。因为我们不会对这个边界进行动画处理。
return;
}
const clones = state.clones;
// If there are no clones at this point, that should mean that there are no
// HostComponent children in this ViewTransition.
//
// 如果此时没有克隆,这应该意味着在此 ViewTransition 中没有 HostComponent 子项。
if (clones !== null) {
applyViewTransitionToClones(oldName, className, clones, finishedWork);
}
}
9. 递归插入新
function recursivelyInsertNew(
parentFiber: Fiber,
hostParentClone: Instance,
parentViewTransition: null | ViewTransitionState,
visitPhase: VisitPhase,
): void {
if (
visitPhase === INSERT_APPEARING_PAIR &&
parentViewTransition === null &&
(parentFiber.subtreeFlags & ViewTransitionNamedStatic) === NoFlags
) {
// We're just searching for pairs but we have reached the end.
// 我们只是在寻找配对,但我们已经到达了尽头。
return;
}
let child = parentFiber.child;
while (child !== null) {
recursivelyInsertNewFiber(
child,
hostParentClone,
parentViewTransition,
visitPhase,
);
child = child.sibling;
}
}
10. 递归插入新Fiber
备注
appendChild()由宿主环境提供trackHostMutation()由 ReactFiberMutationTracking#trackHostMutation 实现pushMutationContext()由 ReactFiberMutationTracking#pushMutationContext 实现popMutationContext()由 ReactFiberMutationTracking#popMutationContext 实现
function recursivelyInsertNewFiber(
finishedWork: Fiber,
hostParentClone: Instance,
parentViewTransition: null | ViewTransitionState,
visitPhase: VisitPhase,
): void {
switch (finishedWork.tag) {
case HostHoistable: {
if (supportsResources) {
// TODO: Hoistables should get optimistically inserted and then removed.
// TODO: 可提升的元素应该先乐观地插入,然后再移除。
recursivelyInsertNew(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
break;
}
// Fall through
}
case HostSingleton: {
if (supportsSingletons) {
recursivelyInsertNew(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
if (__DEV__) {
// We cannot apply mutations to Host Singletons since by definition
// they cannot be cloned. Therefore we warn in DEV if this commit
// had any effect.
//
// 我们无法对 Host 单例应用变更,因为按定义它们无法被克隆。因此,如果此提交有任何
// 影响,我们会在开发环境中发出警告。
if (finishedWork.flags & Update) {
console.error(
'startGestureTransition() caused something to render a new <%s>. ' +
'This is not possible in the current implementation. ' +
"Make sure that the swipe doesn't mount any new <%s> elements.",
finishedWork.type,
finishedWork.type,
);
}
}
break;
}
// Fall through
// 贯穿
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// For insertions we don't need to clone. It's already new state node.
// 对于插入操作,我们不需要克隆。它已经是新的状态节点。
if (visitPhase !== INSERT_APPEARING_PAIR) {
appendChild(hostParentClone, instance);
trackHostMutation();
recursivelyInsertNew(
finishedWork,
instance,
null,
INSERT_APPEARING_PAIR,
);
} else {
recursivelyInsertNew(finishedWork, instance, null, visitPhase);
}
if (parentViewTransition !== null) {
if (parentViewTransition.clones === null) {
parentViewTransition.clones = [instance];
} else {
parentViewTransition.clones.push(instance);
}
}
break;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
if (textInstance === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
// For insertions we don't need to clone. It's already new state node.
// 对于插入操作,我们不需要克隆。它已经是新的状态节点。
if (visitPhase !== INSERT_APPEARING_PAIR) {
appendChild(hostParentClone, textInstance);
trackHostMutation();
}
break;
}
case HostPortal: {
// TODO: Consider what should happen to Portals. For now we exclude them.
// 待办事项:考虑门户应该如何处理。目前我们先将它们排除在外。
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
// Only insert nodes if this tree is going to be visible. No need to
// insert invisible content.
// Since there was no mutation to this node, it couldn't have changed
// visibility so we don't need to update visitPhase here.
//
// 仅在此树将可见时才插入节点。无需插入不可见内容。
// 由于此节点没有发生任何变化,它的可见性不可能改变,所以我们无需在此更新 visitPhase。
recursivelyInsertNew(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
}
break;
}
case ViewTransitionComponent:
const prevMutationContext = pushMutationContext();
const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
// TODO: If this was already cloned by a previous pass we can reuse those clones.
// 待办:如果之前已经被某次处理克隆过,我们可以重用那些克隆。
viewTransitionState.clones = null;
let nextPhase: VisitPhase;
if (visitPhase === INSERT_EXIT) {
// This was an Enter of a ViewTransition. We now move onto inserting the inner
// HostComponents and finding inner pairs.
//
// 这是一个 ViewTransition 的进入。我们现在继续插入内部 HostComponents 并寻找内部配对。
nextPhase = INSERT_APPEND;
} else {
nextPhase = visitPhase;
}
recursivelyInsertNew(
finishedWork,
hostParentClone,
viewTransitionState,
nextPhase,
);
// After we've inserted the new nodes into the "clones" set we can apply share
// or exit transitions to them.
//
// 在我们将新节点插入到“克隆”集合后,我们可以对它们应用共享或退出过渡。
if (visitPhase === INSERT_EXIT) {
applyExitViewTransition(finishedWork);
} else if (
visitPhase === INSERT_APPEARING_PAIR ||
visitPhase === INSERT_APPEND
) {
applyAppearingPairViewTransition(finishedWork);
}
popMutationContext(prevMutationContext);
break;
default: {
recursivelyInsertNew(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
break;
}
}
}
11. 从现有树递归插入克隆
备注
cloneMutableInstance()由宿主环境提供appendChild()由宿主环境提供unhideInstance()由宿主环境提供trackHostMutation()由 ReactFiberMutationTracking#trackHostMutation 实现cloneMutableTextInstance()由宿主环境提供pushMutationContext()由 ReactFiberMutationTracking#pushMutationContext 实现popMutationContext()由 ReactFiberMutationTracking#popMutationContext 实现
function recursivelyInsertClonesFromExistingTree(
parentFiber: Fiber,
hostParentClone: Instance,
parentViewTransition: null | ViewTransitionState,
visitPhase: VisitPhase,
): void {
let child = parentFiber.child;
while (child !== null) {
switch (child.tag) {
case HostComponent: {
const instance: Instance = child.stateNode;
let nextPhase: VisitPhase;
switch (visitPhase) {
case CLONE_EXIT:
case CLONE_UNHIDE:
case CLONE_APPEARING_PAIR:
// If this was an unhide, we need to keep going if there are any named
// pairs in this subtree, since they might need to be marked.
//
// 如果这是一次取消隐藏操作,我们需要继续检查,如果这个子树中有任何命名的
// 对,它们可能需要被标记。
nextPhase =
(child.subtreeFlags & ViewTransitionNamedStatic) !== NoFlags
? CLONE_APPEARING_PAIR
: CLONE_UNCHANGED;
break;
default:
// We've found any "layout" View Transitions at this point so we can bail.
// 到此为止我们没有发现任何“布局”视图过渡,所以我们可以退出。
nextPhase = CLONE_UNCHANGED;
}
let clone: Instance;
if (nextPhase !== CLONE_UNCHANGED) {
// We might need a handle on these clones, so we need to do a shallow clone
// and keep going.
//
// 我们可能需要处理这些克隆,所以我们需要进行浅克隆然后继续。
clone = cloneMutableInstance(instance, false);
recursivelyInsertClonesFromExistingTree(
child,
clone,
null,
nextPhase,
);
} else {
// If we have no mutations in this subtree, and we don't need a handle on the
// clones, then we can do a deep clone instead and bailout.
//
// 如果我们在这个子树中没有任何变更,并且我们不需要对克隆体进行控制,那么我们可以改为进行
// 深度克隆,然后直接退出。
clone = cloneMutableInstance(instance, true);
// TODO: We may need to transfer some DOM state such as scroll position
// for the deep clones.
// TODO: If there's a manual view-transition-name inside the clone we
// should ideally remove it from the original and then restore it in mutation
// phase. Otherwise it leads to duplicate names.
//
// TODO: 我们可能需要传递一些 DOM 状态,例如滚动位置
// 用于深度克隆。
// TODO: 如果克隆中有手动设置的 view-transition-name,理想情况下
// 应该先从原始元素中移除,然后在变更阶段恢复它。
// 否则会导致名称重复。
}
appendChild(hostParentClone, clone);
if (parentViewTransition !== null) {
if (parentViewTransition.clones === null) {
parentViewTransition.clones = [clone];
} else {
parentViewTransition.clones.push(clone);
}
}
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
unhideInstance(clone, child.memoizedProps);
trackHostMutation();
}
break;
}
case HostText: {
const textInstance: TextInstance = child.stateNode;
if (textInstance === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const clone = cloneMutableTextInstance(textInstance);
appendChild(hostParentClone, clone);
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
unhideTextInstance(clone, child.memoizedProps);
trackHostMutation();
}
break;
}
case HostPortal: {
// TODO: Consider what should happen to Portals. For now we exclude them.
// 只有在该树将可见时才插入克隆。无需克隆不可见内容。
// TODO: 如果它是可见的但已分离,它仍然应被克隆。
// 由于此节点没有发生变异,它的可见性不会改变,因此我们这里不需要更新 visitPhase。
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = child.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
// Only insert clones if this tree is going to be visible. No need to
// clone invisible content.
// TODO: If this is visible but detached it should still be cloned.
// Since there was no mutation to this node, it couldn't have changed
// visibility so we don't need to update visitPhase here.
recursivelyInsertClonesFromExistingTree(
child,
hostParentClone,
parentViewTransition,
visitPhase,
);
}
break;
}
case ViewTransitionComponent:
const prevMutationContext = pushMutationContext();
const viewTransitionState: ViewTransitionState = child.stateNode;
// TODO: If this was already cloned by a previous pass we can reuse those clones.
// 待办:如果之前已经被克隆过,我们可以重用那些克隆。
viewTransitionState.clones = null;
// "Existing" view transitions are in subtrees that didn't update so
// this is a "current". We normally clear this upon rerendering
// but we use this flag to track changes from layout in the commit.
// So we need it to be cleared before we do that.
// TODO: Use some other temporary state to track this.
//
// “现有”的视图过渡位于未更新的子树中,所以这是一个“当前的”。我们通常会在重新渲染时清除它
// 但我们使用这个标记来跟踪提交阶段布局的变化。
// 因此,在执行此操作之前需要清除它。
// TODO: 使用其他临时状态来跟踪此信息。
child.flags &= ~Update;
let nextPhase: VisitPhase;
if (visitPhase === CLONE_EXIT) {
// This was an Enter of a ViewTransition. We now move onto unhiding the inner
// HostComponents and finding inner pairs.
//
// 这是一个 ViewTransition 的进入操作。我们现在继续显示内部的 HostComponents 并
// 查找内部成对元素。
nextPhase = CLONE_UNHIDE;
// TODO: Mark the name and find a pair.
} else if (visitPhase === CLONE_UPDATE) {
// If the tree had no mutations and we've found the top most ViewTransition
// then this is the one we might apply the "layout" state too if it has changed
// position. After we've found its HostComponents we can bail out.
//
// 如果树没有变异,并且我们已经找到了最上层的 ViewTransition
// 那么如果它的位置发生了变化,这可能是我们应用“布局”状态的地方。
// 在找到它的 HostComponents 之后,我们可以退出。
nextPhase = CLONE_UNCHANGED;
} else {
nextPhase = visitPhase;
}
recursivelyInsertClonesFromExistingTree(
child,
hostParentClone,
viewTransitionState,
nextPhase,
);
// After we've collected the cloned instances, we can apply exit or share transitions
// to them.
//
// 在收集克隆实例之后,我们可以对它们应用退出或共享转换。
if (visitPhase === CLONE_EXIT) {
applyExitViewTransition(child);
} else if (
visitPhase === CLONE_APPEARING_PAIR ||
visitPhase === CLONE_UNHIDE
) {
applyAppearingPairViewTransition(child);
} else if (visitPhase === CLONE_UPDATE) {
applyNestedViewTransition(child);
}
popMutationContext(prevMutationContext);
break;
default: {
recursivelyInsertClonesFromExistingTree(
child,
hostParentClone,
parentViewTransition,
visitPhase,
);
break;
}
}
child = child.sibling;
}
}
12. 递归插入克隆
备注
trackHostMutation()由 ReactFiberMutationTracking#trackHostMutation 实现
function recursivelyInsertClones(
parentFiber: Fiber,
hostParentClone: Instance,
parentViewTransition: null | ViewTransitionState,
visitPhase: VisitPhase,
) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
trackEnterViewTransitions(childToDelete);
// Normally we would only mark something as triggering a mutation if there was
// actually a HostInstance below here. If this tree didn't contain a HostInstances
// we shouldn't trigger a mutation even though a virtual component was deleted.
//
// 通常我们只有在下面实际上存在 HostInstance 时才会将某些事情标记为触发变更。
// 如果这个树中不包含 HostInstance,即使虚拟组件被删除,我们也不应该触发变更。
trackHostMutation();
}
}
if (
parentFiber.alternate === null ||
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
) {
// If we have mutations or if this is a newly inserted tree, clone as we go.
// 如果我们有变异,或者这是新插入的树,则在操作时进行克隆。
let child = parentFiber.child;
while (child !== null) {
insertDestinationClonesOfFiber(
child,
hostParentClone,
parentViewTransition,
visitPhase,
);
child = child.sibling;
}
} else {
// Once we reach a subtree with no more mutations we can bail out.
// However, we must still insert deep clones of the HostComponents.
//
// 一旦我们到达没有更多变异的子树,我们就可以退出。
// 但是,我们仍然必须插入 HostComponents 的深拷贝。
recursivelyInsertClonesFromExistingTree(
parentFiber,
hostParentClone,
parentViewTransition,
visitPhase,
);
}
}
13. 插入目标光纤克隆
备注
pushMutationContext()由 ReactFiberMutationTracking#pushMutationContext 实现commitUpdate()由宿主环境提供popMutationContext()由 ReactFiberMutationTracking#popMutationContext 实现cloneMutableInstance()由宿主环境提供resetTextContent()由宿主环境提供trackHostMutation()由 ReactFiberMutationTracking#trackHostMutation 实现commitUpdate()由宿主环境提供appendChild()由宿主环境提供unhideInstance()由宿主环境提供commitTextUpdate()由宿主环境提供cancelRootViewTransitionName()由宿主环境提供
function insertDestinationClonesOfFiber(
finishedWork: Fiber,
hostParentClone: Instance,
parentViewTransition: null | ViewTransitionState,
visitPhase: VisitPhase,
) {
const current = finishedWork.alternate;
if (current === null) {
// This is a newly mounted subtree. Insert any HostComponents and trigger
// Enter transitions.
// 这是一个新挂载的子树。插入任何宿主组件并触发进入过渡。
recursivelyInsertNewFiber(
finishedWork,
hostParentClone,
parentViewTransition,
INSERT_EXIT,
);
return;
}
const flags = finishedWork.flags;
// The effect flag should be checked *after* we refine the type of fiber,
// because the fiber tag is more specific. An exception is any flag related
// to reconciliation, because those can be set on all fiber types.
//
// 应该在我们细化 fiber 类型 *之后* 检查 effect 标志,因为 fiber 标签更具体。例外情况是
// 与协调相关的任何标志,因为这些可以在所有 fiber 类型上设置。
switch (finishedWork.tag) {
case HostHoistable: {
if (supportsResources) {
// TODO: Hoistables should get optimistically inserted and then removed.
// TODO: 可提升的元素应该先乐观地插入,然后再移除。
recursivelyInsertClones(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
break;
}
// Fall through
// 贯穿
}
case HostSingleton: {
if (supportsSingletons) {
recursivelyInsertClones(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
if (__DEV__) {
// We cannot apply mutations to Host Singletons since by definition
// they cannot be cloned. Therefore we warn in DEV if this commit
// had any effect.
//
// 我们无法对 Host 单例应用变更,因为按定义它们无法被克隆。因此,如果此提交
// 有任何影响,我们会在开发环境中发出警告。
if (flags & Update) {
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const instance = finishedWork.stateNode;
const type = finishedWork.type;
const prev = pushMutationContext();
try {
// Since we currently don't have a separate diffing algorithm for
// individual properties, the Update flag can be a false positive.
// We have to apply the new props first o detect any mutations and
// then revert them.
//
// 由于我们目前没有针对单独属性的差异算法,Update 标志可能会出现误报。
// 我们必须先应用新的属性以检测任何变更,然后再将它们还原。
commitUpdate(instance, type, oldProps, newProps, finishedWork);
if (viewTransitionMutationContext) {
console.error(
'startGestureTransition() caused something to mutate <%s>. ' +
'This is not possible in the current implementation. ' +
"Make sure that the swipe doesn't update any state which " +
'causes <%s> to change.',
finishedWork.type,
finishedWork.type,
);
}
// Revert
// 撤销
commitUpdate(instance, type, newProps, oldProps, finishedWork);
} finally {
popMutationContext(prev);
}
}
}
break;
}
// Fall through
// 贯穿
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
let clone: Instance;
if (finishedWork.child === null) {
// This node is terminal. We still do a deep clone in case this has user
// inserted content, text content or dangerouslySetInnerHTML.
//
// 这个节点是终端节点。我们仍然会进行深度克隆,以防它包含用户插入的内容、文本
// 内容或 dangerouslySetInnerHTML。
clone = cloneMutableInstance(instance, true);
if (finishedWork.flags & ContentReset) {
resetTextContent(clone);
trackHostMutation();
}
} else {
// If we have children we'll clone them as we walk the tree so we just
// do a shallow clone here.
//
// 如果我们有子节点,我们将在遍历树的过程中克隆它们,所以这里我们只做一个浅拷贝。
clone = cloneMutableInstance(instance, false);
}
if (flags & Update) {
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const type = finishedWork.type;
// Apply the delta to the clone.
// 将增量应用到克隆上。
commitUpdate(clone, type, oldProps, newProps, finishedWork);
}
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
appendChild(hostParentClone, clone);
unhideInstance(clone, finishedWork.memoizedProps);
recursivelyInsertClones(
finishedWork,
clone,
null,
CLONE_APPEARING_PAIR,
);
trackHostMutation();
} else {
appendChild(hostParentClone, clone);
recursivelyInsertClones(finishedWork, clone, null, visitPhase);
}
if (parentViewTransition !== null) {
if (parentViewTransition.clones === null) {
parentViewTransition.clones = [clone];
} else {
parentViewTransition.clones.push(clone);
}
}
break;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
if (textInstance === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const clone = cloneMutableTextInstance(textInstance);
if (flags & Update) {
const newText: string = finishedWork.memoizedProps;
const oldText: string = current.memoizedProps;
commitTextUpdate(clone, newText, oldText);
trackHostMutation();
}
appendChild(hostParentClone, clone);
if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
unhideTextInstance(clone, finishedWork.memoizedProps);
trackHostMutation();
}
break;
}
case HostPortal: {
// TODO: Consider what should happen to Portals. For now we exclude them.
// 待办事项:考虑门户应该如何处理。目前我们先将其排除。
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
// Only insert clones if this tree is going to be visible. No need to
// clone invisible content.
// TODO: If this is visible but detached it should still be cloned.
//
// 只有在这个树将被显示时才插入克隆。没有必要克隆不可见的内容。
// TODO: 如果它是可见的但被分离,它仍然应该被克隆。
let nextPhase: VisitPhase;
if (visitPhase === CLONE_UPDATE && (flags & Visibility) !== NoFlags) {
// This is the root of an appear. We need to trigger Enter transitions.
// 这是出现的根节点。我们需要触发 Enter 过渡。
nextPhase = CLONE_EXIT;
} else {
nextPhase = visitPhase;
}
recursivelyInsertClones(
finishedWork,
hostParentClone,
parentViewTransition,
nextPhase,
);
} else if (current !== null && current.memoizedState === null) {
// Was previously mounted as visible but is now hidden.
// 之前是挂载为可见的,但现在已隐藏。
trackEnterViewTransitions(current);
// Normally we would only mark something as triggering a mutation if there was
// actually a HostInstance below here. If this tree didn't contain a HostInstances
// we shouldn't trigger a mutation even though a virtual component was hidden.
//
// 通常我们只有在下面实际上存在 HostInstance 时才会将某些内容标记为触发变更。
// 如果这个树中不包含 HostInstance,即使隐藏了一个虚拟组件,我们也不应该触发变更。
trackHostMutation();
}
break;
}
case ViewTransitionComponent:
const prevMutationContext = pushMutationContext();
const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
// TODO: If this was already cloned by a previous pass we can reuse those clones.
// 待办:如果之前已经被克隆过,我们可以重用那些克隆。
viewTransitionState.clones = null;
let nextPhase: VisitPhase;
if (visitPhase === CLONE_EXIT) {
// This was an Enter of a ViewTransition. We now move onto unhiding the inner
// HostComponents and finding inner pairs.
// 这是一个视图过渡的进入。现在我们继续显示内部 HostComponents 并查找内部成对元素。
nextPhase = CLONE_UNHIDE;
// TODO: Mark the name and find a pair.
// 待办:标记名称并找到一对。
} else {
nextPhase = visitPhase;
}
recursivelyInsertClones(
finishedWork,
hostParentClone,
viewTransitionState,
nextPhase,
);
if (viewTransitionMutationContext) {
// Track that this boundary had a mutation and therefore needs to animate
// whether it resized or not.
// 跟踪该边界是否有变更,因此需要进行动画处理
// 不论其是否调整了大小。
finishedWork.flags |= Update;
}
// After we've collected the cloned instances, we can apply exit or share transitions
// to them.
// 在收集克隆实例之后,我们可以对它们应用退出或共享过渡。
if (visitPhase === CLONE_EXIT) {
applyExitViewTransition(finishedWork);
} else if (
visitPhase === CLONE_APPEARING_PAIR ||
visitPhase === CLONE_UNHIDE
) {
applyAppearingPairViewTransition(finishedWork);
} else if (visitPhase === CLONE_UPDATE) {
applyUpdateViewTransition(current, finishedWork);
}
popMutationContext(prevMutationContext);
break;
default: {
recursivelyInsertClones(
finishedWork,
hostParentClone,
parentViewTransition,
visitPhase,
);
break;
}
}
}
14. 测量退出视图过渡
function measureExitViewTransitions(placement: Fiber): void {
if (placement.tag === ViewTransitionComponent) {
// const state: ViewTransitionState = placement.stateNode;
const props: ViewTransitionProps = placement.memoizedProps;
const name = props.name;
if (name != null && name !== 'auto') {
// TODO: Find a pair
// 待办:找到一对
}
} else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
// TODO: Check if this is a hidden Offscreen or a Portal.
// TODO: 检查这是否是隐藏的离屏或门户。
let child = placement.child;
while (child !== null) {
measureExitViewTransitions(child);
child = child.sibling;
}
} else {
// We don't need to find pairs here because we would've already found and
// measured the pairs inside the deletion phase.
//
// 我们这里不需要寻找配对,因为我们已经在删除阶段找到并测量了配对。
}
}
15. 递归应用视图过渡
备注
commitEnterViewTransitions()由 ReactFiberCommitViewTransitions#commitEnterViewTransitions 实现measureNestedViewTransitions()由 ReactFiberCommitViewTransitions#measureNestedViewTransitions 实现
function recursivelyApplyViewTransitions(parentFiber: Fiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitEnterViewTransitions(childToDelete, true);
}
}
if (
parentFiber.alternate === null ||
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
) {
// If we have mutations or if this is a newly inserted tree, clone as we go.
// 如果我们有变异,或者这是新插入的树,则在操作时进行克隆。
let child = parentFiber.child;
while (child !== null) {
applyViewTransitionsOnFiber(child);
child = child.sibling;
}
} else {
// Nothing has changed in this subtree, but the parent may have still affected
// its size and position. We need to measure the old and new state to see if
// we should animate its size and position.
//
// 这个子树没有任何变化,但父元素可能仍然影响了它的大小和位置。我们需要测量旧状态和新状态,以
// 确定是否应该对它的大小和位置进行动画处理。
measureNestedViewTransitions(parentFiber, true);
}
}
16. 在 Fiber 上应用视图过渡
备注
commitEnterViewTransitions()由 ReactFiberCommitViewTransitions#commitEnterViewTransitions 实现pushViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#pushViewTransitionCancelableScope 实现measureUpdateViewTransition()由 ReactFiberCommitViewTransitions#measureUpdateViewTransition 实现popViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#popViewTransitionCancelableScope 实现
function applyViewTransitionsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
if (current === null) {
measureExitViewTransitions(finishedWork);
return;
}
const flags = finishedWork.flags;
// The effect flag should be checked *after* we refine the type of fiber,
// because the fiber tag is more specific. An exception is any flag related
// to reconciliation, because those can be set on all fiber types.
//
// 应该在我们细化 fiber 类型 *之后* 检查 effect 标志,
// 因为 fiber 标签更具体。例外情况是与协调相关的任何标志,
// 因为这些可以在所有 fiber 类型上设置。
switch (finishedWork.tag) {
case HostPortal: {
// TODO: Consider what should happen to Portals. For now we exclude them.
// 待办事项:考虑门户应该如何处理。目前我们先将其排除。
break;
}
case OffscreenComponent: {
if (flags & Visibility) {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
measureExitViewTransitions(finishedWork);
} else if (current !== null && current.memoizedState === null) {
// Was previously mounted as visible but is now hidden.
// 之前是挂载为可见的,但现在已隐藏。
commitEnterViewTransitions(current, true);
}
}
break;
}
case ViewTransitionComponent: {
const prevContextChanged = viewTransitionContextChanged;
const prevCancelableChildren = pushViewTransitionCancelableScope();
viewTransitionContextChanged = false;
recursivelyApplyViewTransitions(finishedWork);
if (viewTransitionContextChanged) {
finishedWork.flags |= Update;
}
const inViewport = measureUpdateViewTransition(
current,
finishedWork,
true,
);
if ((finishedWork.flags & Update) === NoFlags || !inViewport) {
// If this boundary didn't update, then we may be able to cancel its children.
// We bubble them up to the parent set to be determined later if we can cancel.
// Similarly, if old and new state was outside the viewport, we can skip it
// even if it did update.
//
// 如果这个边界没有更新,那么我们可能可以取消它的子节点。
// 我们将它们上升到父节点,稍后再确定是否可以取消。
// 同样地,如果旧状态和新状态都在视口之外,即使它确实更新了,我们也可以跳过它。
if (prevCancelableChildren === null) {
// Bubbling up this whole set to the parent.
// 将整个集合冒泡到父级。
} else {
// Merge with parent set.
// 与父集合合并。
prevCancelableChildren.push.apply(
prevCancelableChildren,
viewTransitionCancelableChildren,
);
popViewTransitionCancelableScope(prevCancelableChildren);
}
// TODO: If this doesn't end up canceled, because a parent animates,
// then we should probably issue an event since this instance is part of it.
//
// 待办事项:如果这个操作最终没有被取消,因为父级在动画中,
// 那么我们可能应该触发一个事件,因为这个实例是其中的一部分。
} else {
// TODO: Schedule gesture events.
// If this boundary did update, we cannot cancel its children so those are dropped.
//
// 待办事项:安排手势事件。
// 如果此边界已更新,我们无法取消它的子项,因此这些子项会被丢弃。
popViewTransitionCancelableScope(prevCancelableChildren);
}
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {
// This boundary changed size in a way that may have caused its parent to
// relayout. We need to bubble this information up to the parent.
//
// 这个边界的尺寸发生了变化,可能导致其父级重新布局。
// 我们需要将这个信息传递给父级。
viewTransitionContextChanged = true;
} else {
// Otherwise, we restore it to whatever the parent had found so far.
// 否则,我们将其恢复为父级目前为止找到的内容。
viewTransitionContextChanged = prevContextChanged;
}
const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
// 重置
viewTransitionState.clones = null; // Reset
break;
}
default: {
recursivelyApplyViewTransitions(finishedWork);
break;
}
}
}
17. 递归恢复视图过渡
备注
restoreEnterOrExitViewTransitions()由 ReactFiberCommitViewTransitions#restoreEnterOrExitViewTransitions 实现restoreNestedViewTransitions()由 ReactFiberCommitViewTransitions#restoreNestedViewTransitions 实现
function recursivelyRestoreViewTransitions(parentFiber: Fiber) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
restoreEnterOrExitViewTransitions(childToDelete);
}
}
if (
parentFiber.alternate === null ||
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
) {
// If we have mutations or if this is a newly inserted tree, clone as we go.
// 如果我们有变异,或者这是新插入的树,则在操作时进行克隆。
let child = parentFiber.child;
while (child !== null) {
restoreViewTransitionsOnFiber(child);
child = child.sibling;
}
} else {
// Nothing has changed in this subtree, but the parent may have still affected
// its size and position. We need to measure the old and new state to see if
// we should animate its size and position.
//
// 这个子树没有任何变化,但父元素可能仍然影响了它的大小和位置。我们需要测量旧状态和新状态,以
// 确定是否应该对它的大小和位置进行动画处理。
restoreNestedViewTransitions(parentFiber);
}
}
18. 在 Fiber 上恢复视图过渡
备注
restoreEnterOrExitViewTransitions()由 ReactFiberCommitViewTransitions#restoreEnterOrExitViewTransitions 实现restoreUpdateViewTransitionForGesture()由 ReactFiberCommitViewTransitions#restoreUpdateViewTransitionForGesture 实现
function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
if (current === null) {
restoreEnterOrExitViewTransitions(finishedWork);
return;
}
const flags = finishedWork.flags;
// The effect flag should be checked *after* we refine the type of fiber,
// because the fiber tag is more specific. An exception is any flag related
// to reconciliation, because those can be set on all fiber types.
//
// 应该在我们细化 fiber 类型 *之后* 检查 effect 标志,
// 因为 fiber 标签更具体。例外情况是与协调相关的任何标志,
// 因为这些可以在所有 fiber 类型上设置。
switch (finishedWork.tag) {
case HostPortal: {
// TODO: Consider what should happen to Portals. For now we exclude them.
// 待办事项:考虑门户应该如何处理。目前我们先将其排除。
break;
}
case OffscreenComponent: {
if (flags & Visibility) {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
restoreEnterOrExitViewTransitions(finishedWork);
} else if (current !== null && current.memoizedState === null) {
// Was previously mounted as visible but is now hidden.
// 之前是挂载为可见的,但现在已隐藏。
restoreEnterOrExitViewTransitions(current);
}
}
break;
}
case ViewTransitionComponent:
restoreUpdateViewTransitionForGesture(current, finishedWork);
recursivelyRestoreViewTransitions(finishedWork);
break;
default: {
recursivelyRestoreViewTransitions(finishedWork);
break;
}
}
}