]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense): defer clearing fallback vnode el in case it has dirs (#14080)
authorlinzhe <40790268+linzhe141@users.noreply.github.com>
Mon, 24 Nov 2025 07:20:45 +0000 (15:20 +0800)
committerGitHub <noreply@github.com>
Mon, 24 Nov 2025 07:20:45 +0000 (15:20 +0800)
close #14078

packages/runtime-core/__tests__/components/Suspense.spec.ts
packages/runtime-core/src/components/Suspense.ts

index c6683d6a257aa13d9f6516794ee21ede888bc964..4e8da3288f12d233b0e49196fc9e493136cb5de2 100644 (file)
@@ -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', () => {
         `<div>444</div><div>555</div><div>666</div>`,
       )
     })
+
+    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)
+    })
   })
 })
index d14e96be3d0b64558f12fa90fe51bb993bbea644..0c8b6c28e8dbdb71d6715957368d77b13f8f18a4 100644 (file)
@@ -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) {