Просмотр исходного кода

fix(runtime-vapor): clean up KeepAlive teleport vapor child unmounts in vdom fragments (#14743)

edison 5 дней назад
Родитель
Сommit
05426e0fca

+ 57 - 0
packages/runtime-vapor/__tests__/vdomInterop.spec.ts

@@ -2611,6 +2611,63 @@ describe('vdomInterop', () => {
       }
     })
 
+    test('should remove teleported content when unmounting comment-wrapped vdom vapor child inside VaporKeepAlive', async () => {
+      const show = ref(true)
+      const target = document.createElement('div')
+      target.id = 'comment-wrapped-vdom-vapor-child-teleport-target'
+      document.body.appendChild(target)
+
+      const VaporChild = defineVaporComponent({
+        setup() {
+          return createComponent(VaporKeepAlive, null, {
+            default: withVaporCtx(() =>
+              createComponent(
+                VaporTeleport,
+                {
+                  to: () => '#comment-wrapped-vdom-vapor-child-teleport-target',
+                },
+                {
+                  default: () => template('<input>')(),
+                },
+              ),
+            ),
+          })
+        },
+      })
+
+      const App = defineComponent({
+        setup() {
+          return () =>
+            h('div', null, [
+              show.value
+                ? h(VDomCommentWrapper as any, null, {
+                    default: () => h(VaporChild as any),
+                  })
+                : null,
+            ])
+        },
+      })
+
+      const host = document.createElement('div')
+      const app = createApp(App)
+      app.use(vaporInteropPlugin)
+      app.mount(host)
+
+      try {
+        await nextTick()
+        expect(target.innerHTML).toBe('<input>')
+
+        show.value = false
+        await nextTick()
+
+        expect(target.innerHTML).toBe('')
+      } finally {
+        app.unmount()
+        host.remove()
+        target.remove()
+      }
+    })
+
     test('should update props on reactivation of vapor child in vdom KeepAlive', async () => {
       const VaporChild = defineVaporComponent({
         props: { msg: String },

+ 12 - 7
packages/runtime-vapor/src/vdomInterop.ts

@@ -279,12 +279,17 @@ const vaporInteropImpl: Omit<
     if (instance) {
       // the async component may not be resolved yet, block is null
       if (instance.block) {
+        const anchor = vnode.anchor as Node | null
         unmountComponent(instance, container)
         if (!doRemove) {
           // When the surrounding VDOM fragment owns DOM removal, we still need
           // to dispose the vapor-returned block tree so nested interop state
-          // (for example forwarded VDOM slots) does not stay subscribed.
-          remove(instance.block, undefined)
+          // (for example forwarded VDOM slots or nested KeepAlive cleanup)
+          // does not stay subscribed.
+          const blockContainer = shouldUseCurrentParent(instance.block)
+            ? ((anchor && anchor.parentNode) as ParentNode)
+            : undefined
+          remove(instance.block, blockContainer)
         }
       }
     } else if (vnode.vb) {
@@ -295,7 +300,7 @@ const vaporInteropImpl: Omit<
       // to remove its current block and reach nested Teleport cleanup.
       const blockContainer =
         container ||
-        (needsSlotBlockUnmountContainer(vnode.vb)
+        (shouldUseCurrentParent(vnode.vb)
           ? ((anchor && anchor.parentNode) as ParentNode)
           : undefined)
       remove(vnode.vb, blockContainer)
@@ -1404,15 +1409,15 @@ function renderVDOMSlot(
   return frag
 }
 
-function needsSlotBlockUnmountContainer(block: Block): boolean {
+function shouldUseCurrentParent(block: Block): boolean {
   if (isVaporComponent(block)) {
-    return isKeepAlive(block) || needsSlotBlockUnmountContainer(block.block)
+    return isKeepAlive(block) || shouldUseCurrentParent(block.block)
   }
   if (isArray(block)) {
-    return block.some(needsSlotBlockUnmountContainer)
+    return block.some(shouldUseCurrentParent)
   }
   if (isFragment(block)) {
-    return needsSlotBlockUnmountContainer(block.nodes)
+    return shouldUseCurrentParent(block.nodes)
   }
   return false
 }