diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
index 5badb04b006..9fe381ff645 100644
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ b/packages/runtime-core/src/componentRenderUtils.ts
@@ -60,6 +60,7 @@ export function renderComponentRoot(
setupState,
ctx,
inheritAttrs,
+ isMounted,
} = instance
const prev = setCurrentRenderingInstance(instance)
@@ -253,7 +254,9 @@ export function renderComponentRoot(
`that cannot be animated.`,
)
}
- root.transition = vnode.transition
+ root.transition = isMounted
+ ? vnode.component!.subTree.transition!
+ : vnode.transition
}
if (__DEV__ && setRoot) {
diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts
index 37534ad699f..568a6382bfe 100644
--- a/packages/runtime-core/src/components/BaseTransition.ts
+++ b/packages/runtime-core/src/components/BaseTransition.ts
@@ -227,6 +227,7 @@ const BaseTransitionImpl: ComponentOptions = {
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
instance.update()
}
+ delete leavingHooks.afterLeave
}
return emptyPlaceholder(child)
} else if (mode === 'in-out' && innerChild.type !== Comment) {
diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts
index a87f44cc8fa..dd1d1f5a6e3 100644
--- a/packages/runtime-core/src/components/KeepAlive.ts
+++ b/packages/runtime-core/src/components/KeepAlive.ts
@@ -267,7 +267,7 @@ const KeepAliveImpl: ComponentOptions = {
pendingCacheKey = null
if (!slots.default) {
- return null
+ return (current = null)
}
const children = slots.default()
diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts
index b9e9117289e..8cdda4dc63e 100644
--- a/packages/vue/__tests__/e2e/Transition.spec.ts
+++ b/packages/vue/__tests__/e2e/Transition.spec.ts
@@ -1427,9 +1427,11 @@ describe('e2e: Transition', () => {
},
E2E_TIMEOUT,
)
+ })
+ describe('transition with KeepAlive', () => {
test(
- 'w/ KeepAlive + unmount innerChild',
+ 'unmount innerChild (out-in mode)',
async () => {
const unmountSpy = vi.fn()
await page().exposeFunction('unmountSpy', unmountSpy)
@@ -1484,6 +1486,173 @@ describe('e2e: Transition', () => {
},
E2E_TIMEOUT,
)
+
+ // #11775
+ test(
+ 'switch child then update include (out-in mode)',
+ async () => {
+ const onUpdatedSpyA = vi.fn()
+ const onUnmountedSpyC = vi.fn()
+
+ await page().exposeFunction('onUpdatedSpyA', onUpdatedSpyA)
+ await page().exposeFunction('onUnmountedSpyC', onUnmountedSpyC)
+
+ await page().evaluate(() => {
+ const { onUpdatedSpyA, onUnmountedSpyC } = window as any
+ const { createApp, ref, shallowRef, h, onUpdated, onUnmounted } = (
+ window as any
+ ).Vue
+ createApp({
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ components: {
+ CompA: {
+ name: 'CompA',
+ setup() {
+ onUpdated(onUpdatedSpyA)
+ return () => h('div', 'CompA')
+ },
+ },
+ CompB: {
+ name: 'CompB',
+ setup() {
+ return () => h('div', 'CompB')
+ },
+ },
+ CompC: {
+ name: 'CompC',
+ setup() {
+ onUnmounted(onUnmountedSpyC)
+ return () => h('div', 'CompC')
+ },
+ },
+ },
+ setup: () => {
+ const includeRef = ref(['CompA', 'CompB', 'CompC'])
+ const current = shallowRef('CompA')
+ const switchToB = () => (current.value = 'CompB')
+ const switchToC = () => (current.value = 'CompC')
+ const switchToA = () => {
+ current.value = 'CompA'
+ includeRef.value = ['CompA']
+ }
+ return { current, switchToB, switchToC, switchToA, includeRef }
+ },
+ }).mount('#app')
+ })
+
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompA
')
+
+ await click('#switchToB')
+ await nextTick()
+ await click('#switchToC')
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompC
')
+
+ await click('#switchToA')
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompA
')
+
+ // expect CompA only update once
+ expect(onUpdatedSpyA).toBeCalledTimes(1)
+ expect(onUnmountedSpyC).toBeCalledTimes(1)
+ },
+ E2E_TIMEOUT,
+ )
+
+ // #10827
+ test(
+ 'switch and update child then update include (out-in mode)',
+ async () => {
+ const onUnmountedSpyB = vi.fn()
+ await page().exposeFunction('onUnmountedSpyB', onUnmountedSpyB)
+
+ await page().evaluate(() => {
+ const { onUnmountedSpyB } = window as any
+ const {
+ createApp,
+ ref,
+ shallowRef,
+ h,
+ provide,
+ inject,
+ onUnmounted,
+ } = (window as any).Vue
+ createApp({
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ components: {
+ CompA: {
+ name: 'CompA',
+ setup() {
+ const current = inject('current')
+ return () => h('div', current.value)
+ },
+ },
+ CompB: {
+ name: 'CompB',
+ setup() {
+ const current = inject('current')
+ onUnmounted(onUnmountedSpyB)
+ return () => h('div', current.value)
+ },
+ },
+ },
+ setup: () => {
+ const includeRef = ref(['CompA'])
+ const current = shallowRef('CompA')
+ provide('current', current)
+
+ const switchToB = () => {
+ current.value = 'CompB'
+ includeRef.value = ['CompA', 'CompB']
+ }
+ const switchToA = () => {
+ current.value = 'CompA'
+ includeRef.value = ['CompA']
+ }
+ return { current, switchToB, switchToA, includeRef }
+ },
+ }).mount('#app')
+ })
+
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompA
')
+
+ await click('#switchToB')
+ await transitionFinish()
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompB
')
+
+ await click('#switchToA')
+ await transitionFinish()
+ await transitionFinish()
+ expect(await html('#container')).toBe('CompA
')
+
+ expect(onUnmountedSpyB).toBeCalledTimes(1)
+ },
+ E2E_TIMEOUT,
+ )
})
describe('transition with Suspense', () => {