Procházet zdrojové kódy

fix(runtime-vapor): preserve teleport child scope for directive cleanup (#14776)

close #14773
edison před 1 měsícem
rodič
revize
ab19c520bb

+ 56 - 0
packages/runtime-vapor/__tests__/components/Teleport.spec.ts

@@ -1231,6 +1231,62 @@ function runSharedTests(deferMode: boolean): void {
     expect(teardown).toHaveBeenCalledTimes(1)
   })
 
+  test(`should run directive cleanup when teleport branch is unmounted`, async () => {
+    const target = document.createElement('div')
+    const root = document.createElement('div')
+    const show = ref(false)
+
+    const spy = vi.fn()
+    const teardown = vi.fn()
+    const dir: VaporDirective = vi.fn(() => {
+      spy()
+      return teardown
+    })
+
+    const { mount } = define({
+      setup() {
+        return createIf(
+          () => show.value,
+          () =>
+            createComponent(
+              VaporTeleport,
+              {
+                to: () => target,
+              },
+              {
+                default: () => {
+                  const n1 = template('<div>foo</div>')() as any
+                  withVaporDirectives(n1, [[dir]])
+                  return n1
+                },
+              },
+            ),
+        )
+      },
+    }).create()
+
+    mount(root)
+    expect(root.innerHTML).toBe('<!--if-->')
+    expect(target.innerHTML).toBe('')
+    expect(spy).not.toHaveBeenCalled()
+
+    show.value = true
+    await nextTick()
+    expect(root.innerHTML).toBe(
+      '<!--teleport start--><!--teleport end--><!--if-->',
+    )
+    expect(target.innerHTML).toBe('<div>foo</div>')
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(teardown).not.toHaveBeenCalled()
+
+    show.value = false
+    await nextTick()
+    expect(root.innerHTML).toBe('<!--if-->')
+    expect(target.innerHTML).toBe('')
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(teardown).toHaveBeenCalledTimes(1)
+  })
+
   test(`ensure that target changes when disabled are updated correctly when enabled`, async () => {
     const root = document.createElement('div')
     const target1 = document.createElement('div')

+ 14 - 8
packages/runtime-vapor/src/components/Teleport.ts

@@ -1,4 +1,4 @@
-import { pauseTracking, resetTracking } from '@vue/reactivity'
+import { getCurrentScope, pauseTracking, resetTracking } from '@vue/reactivity'
 import {
   type GenericComponentInstance,
   MismatchTypes,
@@ -76,6 +76,7 @@ export class TeleportFragment extends VaporFragment {
   private childrenInitialized = false
   private readonly ownerInstance =
     currentInstance as VaporComponentInstance | null
+  private readonly childrenScope = getCurrentScope()
 
   target?: ParentNode | null
   targetAnchor?: Node | null
@@ -125,16 +126,21 @@ export class TeleportFragment extends VaporFragment {
   }
 
   private initChildren(): void {
-    const prevInstance = setCurrentInstance(this.ownerInstance)
+    const prevInstance = setCurrentInstance(
+      this.ownerInstance,
+      this.childrenScope,
+    )
     try {
       this.childrenInitialized = true
       renderEffect(() =>
-        this.runWithRenderCtx(() =>
-          this.handleChildrenUpdate(
-            this.rawSlots && this.rawSlots.default
-              ? (this.rawSlots.default as BlockFn)()
-              : [],
-          ),
+        this.runWithRenderCtx(
+          () =>
+            this.handleChildrenUpdate(
+              this.rawSlots && this.rawSlots.default
+                ? (this.rawSlots.default as BlockFn)()
+                : [],
+            ),
+          this.childrenScope,
         ),
       )
       this.bindChildren(this.nodes)

+ 2 - 2
packages/runtime-vapor/src/fragment.ts

@@ -107,8 +107,8 @@ export class VaporFragment<
     }
   }
 
-  protected runWithRenderCtx<R>(fn: () => R): R {
-    const prevInstance = setCurrentInstance(this.renderInstance)
+  protected runWithRenderCtx<R>(fn: () => R, scope?: EffectScope): R {
+    const prevInstance = setCurrentInstance(this.renderInstance, scope)
     const prevSlotOwner = setCurrentSlotOwner(this.slotOwner)
     let prevKeepAliveCtx: VaporKeepAliveContext | null = null
     if (isKeepAliveEnabled) {