فهرست منبع

fix(teleport): optimize transition handling and prevent unnecessary updates (#14440)

close #14438
edison 2 ماه پیش
والد
کامیت
90ea8ce663
2فایلهای تغییر یافته به همراه79 افزوده شده و 7 حذف شده
  1. 50 0
      packages/runtime-vapor/__tests__/components/Teleport.spec.ts
  2. 29 7
      packages/runtime-vapor/src/components/Teleport.ts

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

@@ -7,6 +7,7 @@ import {
 import {
   type VaporDirective,
   VaporTeleport,
+  VaporTransition,
   child,
   createIf,
   createTemplateRefSetter,
@@ -1329,4 +1330,53 @@ function runSharedTests(deferMode: boolean): void {
     expect(target.innerHTML).toBe('content')
     app.unmount()
   })
+
+  test('avoid unnecessary update and transition', async () => {
+    const target1 = document.createElement('div')
+    const target2 = document.createElement('div')
+
+    const target = shallowRef(target1)
+    const defer = ref(false)
+    const disabled = ref(false)
+    const beforeEnter = vi.fn()
+
+    define({
+      setup() {
+        const n4 = createComponent(
+          VaporTeleport,
+          {
+            defer: () => defer.value,
+            to: () => target.value,
+            disabled: () => disabled.value,
+          },
+          {
+            default: () =>
+              createComponent(
+                VaporTransition,
+                {
+                  onBeforeEnter: () => beforeEnter,
+                },
+                {
+                  default: () => template('<div>Teleport')(),
+                },
+              ),
+          },
+        )
+        return n4
+      },
+    }).render()
+
+    expect(beforeEnter).toHaveBeenCalledTimes(0)
+    defer.value = true
+    await nextTick()
+    expect(beforeEnter).toHaveBeenCalledTimes(0)
+
+    disabled.value = true
+    await nextTick()
+    expect(beforeEnter).toHaveBeenCalledTimes(0)
+
+    target.value = target2
+    await nextTick()
+    expect(beforeEnter).toHaveBeenCalledTimes(0)
+  })
 }

+ 29 - 7
packages/runtime-vapor/src/components/Teleport.ts

@@ -1,6 +1,7 @@
 import {
   type GenericComponentInstance,
   MismatchTypes,
+  MoveType,
   type TeleportProps,
   type TeleportTargetElement,
   currentInstance,
@@ -16,6 +17,7 @@ import {
   type BlockFn,
   applyTransitionHooks,
   insert,
+  move,
   remove,
 } from '../block'
 import { createComment, createTextNode, querySelector } from '../dom/node'
@@ -60,6 +62,7 @@ export class TeleportFragment extends VaporFragment {
   private resolvedProps?: TeleportProps
   private rawSlots?: LooseRawSlots
   isDisabled?: boolean
+  private isMounted = false
 
   target?: ParentNode | null
   targetAnchor?: Node | null
@@ -81,6 +84,8 @@ export class TeleportFragment extends VaporFragment {
         : createTextNode()
 
     renderEffect(() => {
+      const prevTo = this.resolvedProps && this.resolvedProps.to
+      const wasDisabled = this.isDisabled
       // access the props to trigger tracking
       this.resolvedProps = extend(
         {},
@@ -89,8 +94,11 @@ export class TeleportFragment extends VaporFragment {
           rawPropsProxyHandlers,
         ) as any as TeleportProps,
       )
+
       this.isDisabled = isTeleportDisabled(this.resolvedProps!)
-      this.handlePropsUpdate()
+      if (wasDisabled !== this.isDisabled || prevTo !== this.resolvedProps.to) {
+        this.handlePropsUpdate()
+      }
     })
 
     if (!isHydrating) {
@@ -154,14 +162,26 @@ export class TeleportFragment extends VaporFragment {
   }
 
   private mount(parent: ParentNode, anchor: Node | null) {
-    if (this.$transition) {
+    // don't apply transitions during move teleports
+    // algin with Vue DOM teleport behavior
+    if (this.$transition && !this.isMounted) {
       applyTransitionHooks(this.nodes, this.$transition)
     }
-    insert(
-      this.nodes,
-      (this.mountContainer = parent),
-      (this.mountAnchor = anchor),
-    )
+    if (this.isMounted) {
+      move(
+        this.nodes,
+        (this.mountContainer = parent),
+        (this.mountAnchor = anchor),
+        MoveType.REORDER,
+      )
+    } else {
+      insert(
+        this.nodes,
+        (this.mountContainer = parent),
+        (this.mountAnchor = anchor),
+      )
+      this.isMounted = true
+    }
   }
 
   private mountToTarget(): void {
@@ -242,6 +262,8 @@ export class TeleportFragment extends VaporFragment {
       this.nodes = []
     }
 
+    this.isMounted = false
+
     // remove anchors
     if (this.targetStart) {
       remove(this.targetStart!, this.target!)