From c0f63ddbfa8fa221d66b683b5c26e471851c2b50 Mon Sep 17 00:00:00 2001 From: linzhe <40790268+linzhe141@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:20:45 +0800 Subject: [PATCH] fix(suspense): defer clearing fallback vnode el in case it has dirs (#14080) close #14078 --- .../__tests__/components/Suspense.spec.ts | 36 +++++++++++++++++++ .../runtime-core/src/components/Suspense.ts | 4 +-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index c6683d6a25..4e8da3288f 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -24,6 +24,7 @@ import { shallowRef, watch, watchEffect, + withDirectives, } from '@vue/runtime-test' import { computed, createApp, defineComponent, inject, provide } from 'vue' import type { RawSlots } from 'packages/runtime-core/src/componentSlots' @@ -2358,5 +2359,40 @@ describe('Suspense', () => { `
444
555
666
`, ) }) + + test('should call unmounted directive once when fallback is replaced by resolved async component', async () => { + const Comp = { + render() { + return h('div', null, 'comp') + }, + } + const Foo = defineAsyncComponent({ + render() { + return h(Comp) + }, + }) + const unmounted = vi.fn(el => { + el.foo = null + }) + const vDir = { + unmounted, + } + const App = { + setup() { + return () => { + return h(Suspense, null, { + fallback: () => withDirectives(h('div'), [[vDir, true]]), + default: () => h(Foo), + }) + } + }, + } + const root = nodeOps.createElement('div') + render(h(App), root) + + await Promise.all(deps) + await nextTick() + expect(unmounted).toHaveBeenCalledTimes(1) + }) }) }) diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index d14e96be3d..0c8b6c28e8 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -20,6 +20,7 @@ import { type RendererInternals, type RendererNode, type SetupRenderEffectFn, + queuePostRenderEffect, } from '../renderer' import { queuePostFlushCb } from '../scheduler' import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils' @@ -576,9 +577,8 @@ function createSuspenseBoundary( } unmount(activeBranch, parentComponent, suspense, true) // clear el reference from fallback vnode to allow GC - // only clear immediately if there's no delayed transition if (!delayEnter && isInFallback && vnode.ssFallback) { - vnode.ssFallback.el = null + queuePostRenderEffect(() => (vnode.ssFallback!.el = null), suspense) } } if (!delayEnter) { -- 2.47.3