From 050ad0edfc0116558315e5962a89036f27cf4545 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 4 Jun 2024 15:46:01 +0800 Subject: [PATCH 1/5] fix(Transition): ensure inner child and enterHooks is fresh after clone --- .../src/components/BaseTransition.ts | 21 ++++++++++++------- packages/runtime-core/src/vnode.ts | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 070418a19a0..dc84901717c 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -61,7 +61,7 @@ export interface TransitionHooks { beforeEnter(el: HostElement): void enter(el: HostElement): void leave(el: HostElement, remove: () => void): void - clone(vnode: VNode): TransitionHooks + cloneTo(vnode: VNode): void // optional afterLeave?(): void delayLeave?( @@ -193,16 +193,20 @@ const BaseTransitionImpl: ComponentOptions = { // in the case of , we need to // compare the type of the kept-alive children. - const innerChild = getKeepAliveChild(child) + let innerChild = getKeepAliveChild(child) if (!innerChild) { return emptyPlaceholder(child) } - const enterHooks = resolveTransitionHooks( + let enterHooks = resolveTransitionHooks( innerChild, rawProps, state, instance, + vnode => { + innerChild = vnode + enterHooks = vnode.transition! + }, ) setTransitionHooks(innerChild, enterHooks) @@ -299,6 +303,7 @@ export function resolveTransitionHooks( props: BaseTransitionProps, state: TransitionState, instance: ComponentInternalInstance, + postClone?: (vnode: VNode) => void, ): TransitionHooks { const { appear, @@ -438,8 +443,10 @@ export function resolveTransitionHooks( } }, - clone(vnode) { - return resolveTransitionHooks(vnode, props, state, instance) + cloneTo(vnode) { + const hooks = resolveTransitionHooks(vnode, props, state, instance) + setTransitionHooks(vnode, hooks) + if (postClone) postClone(vnode) }, } @@ -488,8 +495,8 @@ export function setTransitionHooks(vnode: VNode, hooks: TransitionHooks) { if (vnode.shapeFlag & ShapeFlags.COMPONENT && vnode.component) { setTransitionHooks(vnode.component.subTree, hooks) } else if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) { - vnode.ssContent!.transition = hooks.clone(vnode.ssContent!) - vnode.ssFallback!.transition = hooks.clone(vnode.ssFallback!) + hooks.cloneTo(vnode.ssContent!) + hooks.cloneTo(vnode.ssFallback!) } else { vnode.transition = hooks } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 0e2a4bafcc5..0f37b8f1807 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -691,7 +691,7 @@ export function cloneVNode( // to clone the transition to ensure that the vnode referenced within // the transition hooks is fresh. if (transition && cloneTransition) { - cloned.transition = transition.clone(cloned as VNode) + transition.cloneTo(cloned as VNode) } if (__COMPAT__) { From 7dc31d593d1741a8ae23ac8d8114001526a2f84a Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 4 Jun 2024 15:57:55 +0800 Subject: [PATCH 2/5] chore: tweak --- packages/runtime-core/src/components/BaseTransition.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index dc84901717c..4f77acdd211 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -204,7 +204,6 @@ const BaseTransitionImpl: ComponentOptions = { state, instance, vnode => { - innerChild = vnode enterHooks = vnode.transition! }, ) From 89517188d0fd98e7943ce314df00eb8e26368c78 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 4 Jun 2024 16:09:57 +0800 Subject: [PATCH 3/5] chore: avoid changing TransitionHooks --- .../src/components/BaseTransition.ts | 21 +++++++++---------- packages/runtime-core/src/vnode.ts | 10 +++++++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 4f77acdd211..f7fbfb7f40b 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -61,7 +61,7 @@ export interface TransitionHooks { beforeEnter(el: HostElement): void enter(el: HostElement): void leave(el: HostElement, remove: () => void): void - cloneTo(vnode: VNode): void + clone(vnode: VNode): TransitionHooks // optional afterLeave?(): void delayLeave?( @@ -193,7 +193,7 @@ const BaseTransitionImpl: ComponentOptions = { // in the case of , we need to // compare the type of the kept-alive children. - let innerChild = getKeepAliveChild(child) + const innerChild = getKeepAliveChild(child) if (!innerChild) { return emptyPlaceholder(child) } @@ -203,9 +203,8 @@ const BaseTransitionImpl: ComponentOptions = { rawProps, state, instance, - vnode => { - enterHooks = vnode.transition! - }, + // #11061, ensure enterHooks is fresh after clone + hooks => (enterHooks = hooks), ) setTransitionHooks(innerChild, enterHooks) @@ -302,7 +301,7 @@ export function resolveTransitionHooks( props: BaseTransitionProps, state: TransitionState, instance: ComponentInternalInstance, - postClone?: (vnode: VNode) => void, + postClone?: (hooks: TransitionHooks) => void, ): TransitionHooks { const { appear, @@ -442,10 +441,10 @@ export function resolveTransitionHooks( } }, - cloneTo(vnode) { + clone(vnode) { const hooks = resolveTransitionHooks(vnode, props, state, instance) - setTransitionHooks(vnode, hooks) - if (postClone) postClone(vnode) + if (postClone) postClone(hooks) + return hooks }, } @@ -494,8 +493,8 @@ export function setTransitionHooks(vnode: VNode, hooks: TransitionHooks) { if (vnode.shapeFlag & ShapeFlags.COMPONENT && vnode.component) { setTransitionHooks(vnode.component.subTree, hooks) } else if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) { - hooks.cloneTo(vnode.ssContent!) - hooks.cloneTo(vnode.ssFallback!) + vnode.ssContent!.transition = hooks.clone(vnode.ssContent!) + vnode.ssFallback!.transition = hooks.clone(vnode.ssFallback!) } else { vnode.transition = hooks } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 0f37b8f1807..3d30503e20a 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -36,7 +36,10 @@ import { isSuspense, } from './components/Suspense' import type { DirectiveBinding } from './directives' -import type { TransitionHooks } from './components/BaseTransition' +import { + type TransitionHooks, + setTransitionHooks, +} from './components/BaseTransition' import { warn } from './warning' import { type Teleport, @@ -691,7 +694,10 @@ export function cloneVNode( // to clone the transition to ensure that the vnode referenced within // the transition hooks is fresh. if (transition && cloneTransition) { - transition.cloneTo(cloned as VNode) + setTransitionHooks( + cloned as VNode, + transition.clone(cloned as VNode) as TransitionHooks, + ) } if (__COMPAT__) { From 6629b3319e16b3abbf90fce5e4adb6c50c814959 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 4 Jun 2024 16:51:00 +0800 Subject: [PATCH 4/5] test: add test case --- packages/vue/__tests__/e2e/Transition.spec.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index 4fe78ae8ab0..59754bb97b3 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1263,6 +1263,98 @@ describe('e2e: Transition', () => { E2E_TIMEOUT, ) + // #11061 + test( + 'transition + fallthrough attrs (in-out mode)', + async () => { + const beforeLeaveSpy = vi.fn() + const onLeaveSpy = vi.fn() + const afterLeaveSpy = vi.fn() + const beforeEnterSpy = vi.fn() + const onEnterSpy = vi.fn() + const afterEnterSpy = vi.fn() + + await page().exposeFunction('onLeaveSpy', onLeaveSpy) + await page().exposeFunction('onEnterSpy', onEnterSpy) + await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy) + await page().exposeFunction('beforeEnterSpy', beforeEnterSpy) + await page().exposeFunction('afterLeaveSpy', afterLeaveSpy) + await page().exposeFunction('afterEnterSpy', afterEnterSpy) + + await page().evaluate(() => { + const { onEnterSpy, onLeaveSpy } = window as any + const { createApp, ref } = (window as any).Vue + createApp({ + components: { + one: { + template: '
one
', + }, + two: { + template: '
two
', + }, + }, + template: ` +
+ + + +
+ + `, + setup: () => { + const view = ref('one') + const click = () => + (view.value = view.value === 'one' ? 'two' : 'one') + return { + view, + click, + beforeEnterSpy, + onEnterSpy, + afterEnterSpy, + beforeLeaveSpy, + onLeaveSpy, + afterLeaveSpy, + } + }, + }).mount('#app') + }) + expect(await html('#container')).toBe('
one
') + + // toggle + await click('#toggleBtn') + await nextTick() + await transitionFinish() + expect(beforeEnterSpy).toBeCalledTimes(1) + expect(onEnterSpy).toBeCalledTimes(1) + expect(afterEnterSpy).toBeCalledTimes(1) + expect(beforeLeaveSpy).toBeCalledTimes(1) + expect(onLeaveSpy).toBeCalledTimes(1) + expect(afterLeaveSpy).toBeCalledTimes(1) + + expect(await html('#container')).toBe('
two
') + + // toggle back + await click('#toggleBtn') + await nextTick() + await transitionFinish() + expect(beforeEnterSpy).toBeCalledTimes(2) + expect(onEnterSpy).toBeCalledTimes(2) + expect(afterEnterSpy).toBeCalledTimes(2) + expect(beforeLeaveSpy).toBeCalledTimes(2) + expect(onLeaveSpy).toBeCalledTimes(2) + expect(afterLeaveSpy).toBeCalledTimes(2) + + expect(await html('#container')).toBe('
one
') + }, + E2E_TIMEOUT, + ) + test( 'w/ KeepAlive + unmount innerChild', async () => { From c1c1416264eb414dc36c9b119cb13b0b43086b22 Mon Sep 17 00:00:00 2001 From: edison1105 Date: Tue, 4 Jun 2024 21:44:03 +0800 Subject: [PATCH 5/5] chore: tweak params --- packages/runtime-core/src/components/BaseTransition.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index f7fbfb7f40b..d1751f4955a 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -442,7 +442,13 @@ export function resolveTransitionHooks( }, clone(vnode) { - const hooks = resolveTransitionHooks(vnode, props, state, instance) + const hooks = resolveTransitionHooks( + vnode, + props, + state, + instance, + postClone, + ) if (postClone) postClone(hooks) return hooks },