diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index c6683d6a257..4e8da3288f1 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 d14e96be3d0..0c8b6c28e8d 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) {