From: edison Date: Wed, 14 Jan 2026 12:29:34 +0000 (+0800) Subject: fix(transition): handle transition on pre-resolved async components (#14314) X-Git-Tag: v3.6.0-beta.4~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d30aff79e0125140b7f8d90b0d6e4cd125b7c66;p=thirdparty%2Fvuejs%2Fcore.git fix(transition): handle transition on pre-resolved async components (#14314) --- diff --git a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts index f5ed9427c6..869364dec0 100644 --- a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts +++ b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts @@ -1179,6 +1179,59 @@ describe('vapor transition', () => { '
vapor compA
', ) }) + + test('apply transition to pre-resolved async component', async () => { + const btnSelector = '.async-resolved > button' + const containerSelector = '.async-resolved #container' + const hiddenCompSelector = '.async-resolved #hidden-async' + + // Wait for the hidden AsyncCompResolved to resolve and render + await waitForInnerHTML( + hiddenCompSelector, + '
vapor compA
', + ) + + expect(await html(containerSelector)).toBe('') + + await click(btnSelector) + expect(await html(containerSelector)).toBe( + '
vapor compA
', + ) + await waitForInnerHTML( + containerSelector, + '
vapor compA
', + ) + await waitForInnerHTML( + containerSelector, + '
vapor compA
', + ) + + // leave + await click(btnSelector) + await nextTick() + expect(await html(containerSelector)).toBe( + '
vapor compA
', + ) + await waitForInnerHTML( + containerSelector, + '
vapor compA
', + ) + await waitForInnerHTML(containerSelector, '') + + // enter again + await click(btnSelector) + expect(await html(containerSelector)).toBe( + '
vapor compA
', + ) + await waitForInnerHTML( + containerSelector, + '
vapor compA
', + ) + await waitForInnerHTML( + containerSelector, + '
vapor compA
', + ) + }) }) describe('transition with v-show', () => { diff --git a/packages-private/vapor-e2e-test/transition/App.vue b/packages-private/vapor-e2e-test/transition/App.vue index 8e52aeec21..904a6c4ae9 100644 --- a/packages-private/vapor-e2e-test/transition/App.vue +++ b/packages-private/vapor-e2e-test/transition/App.vue @@ -112,6 +112,10 @@ const AsyncComp = defineVaporAsyncComponent(() => { return new Promise(resolve => setTimeout(() => resolve(VaporCompA), 50)) }) +const AsyncCompResolved = defineVaporAsyncComponent(() => + Promise.resolve(VaporCompA), +) + const TrueBranch = defineVaporComponent({ name: 'TrueBranch', setup() { @@ -649,6 +653,18 @@ const Comp2 = defineVaporComponent({ +
+ +
+ +
+
+ + + +
+ +
diff --git a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts index 0493e2ea1f..fccc28bcb0 100644 --- a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts +++ b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts @@ -28,7 +28,6 @@ import { import { invokeArrayFns } from '@vue/shared' import { type TransitionOptions, insert, remove } from './block' import { parentNode } from './dom/node' -import { setTransitionHooks } from './components/Transition' /*@ __NO_SIDE_EFFECTS__ */ export function defineVaporAsyncComponent( @@ -167,7 +166,6 @@ export function defineVaporAsyncComponent( render = () => createComponent(loadingComponent) } - if (instance.$transition) frag!.$transition = instance.$transition frag.update(render) // Manually trigger cacheBlock for KeepAlive if (frag.keepAliveCtx) frag.keepAliveCtx.cacheBlock() @@ -183,7 +181,7 @@ function createInnerComp( parent: VaporComponentInstance & TransitionOptions, frag?: DynamicFragment, ): VaporComponentInstance { - const { rawProps, rawSlots, appContext, $transition } = parent + const { rawProps, rawSlots, appContext } = parent const instance = createComponent( comp, rawProps, @@ -195,9 +193,6 @@ function createInnerComp( appContext, ) - // set transition hooks - if ($transition) setTransitionHooks(instance, $transition) - // set ref frag && frag.setAsyncRef && frag.setAsyncRef(instance) diff --git a/packages/runtime-vapor/src/components/Transition.ts b/packages/runtime-vapor/src/components/Transition.ts index 62f991c3b4..79c3ed942e 100644 --- a/packages/runtime-vapor/src/components/Transition.ts +++ b/packages/runtime-vapor/src/components/Transition.ts @@ -32,7 +32,11 @@ import { } from '../component' import { isArray } from '@vue/shared' import { renderEffect } from '../renderEffect' -import { type VaporFragment, isFragment } from '../fragment' +import { + type DynamicFragment, + type VaporFragment, + isFragment, +} from '../fragment' import { currentHydrationNode, isHydrating, @@ -288,9 +292,16 @@ export function findTransitionBlock( // transition can only be applied on Element child if (block instanceof Element) child = block } else if (isVaporComponent(block)) { - // should save hooks on unresolved async wrapper, so that it can be applied after resolved - if (isAsyncWrapper(block) && !block.type.__asyncResolved) { - child = block + if (isAsyncWrapper(block)) { + // for unresolved async wrapper, set transition hooks on inner fragment + if (!block.type.__asyncResolved) { + onFragment && onFragment(block.block! as DynamicFragment) + } else { + child = findTransitionBlock( + (block.block! as DynamicFragment).nodes, + onFragment, + ) + } } else { // stop searching if encountering nested Transition component if (getComponentName(block.type) === displayName) return undefined