فهرست منبع

fix(runtime-core): properly handle inherit transition during clone VNode (#10809)

close #3716
close #10497
close #4091
edison 2 سال پیش
والد
کامیت
638a79f64a
3فایلهای تغییر یافته به همراه71 افزوده شده و 8 حذف شده
  1. 11 6
      packages/runtime-core/src/componentRenderUtils.ts
  2. 12 2
      packages/runtime-core/src/vnode.ts
  3. 48 0
      packages/vue/__tests__/e2e/Transition.spec.ts

+ 11 - 6
packages/runtime-core/src/componentRenderUtils.ts

@@ -166,7 +166,7 @@ export function renderComponentRoot(
             propsOptions,
             propsOptions,
           )
           )
         }
         }
-        root = cloneVNode(root, fallthroughAttrs)
+        root = cloneVNode(root, fallthroughAttrs, false, true)
       } else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
       } else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
         const allAttrs = Object.keys(attrs)
         const allAttrs = Object.keys(attrs)
         const eventAttrs: string[] = []
         const eventAttrs: string[] = []
@@ -221,10 +221,15 @@ export function renderComponentRoot(
           getComponentName(instance.type),
           getComponentName(instance.type),
         )
         )
       }
       }
-      root = cloneVNode(root, {
-        class: cls,
-        style: style,
-      })
+      root = cloneVNode(
+        root,
+        {
+          class: cls,
+          style: style,
+        },
+        false,
+        true,
+      )
     }
     }
   }
   }
 
 
@@ -237,7 +242,7 @@ export function renderComponentRoot(
       )
       )
     }
     }
     // clone before mutating since the root may be a hoisted vnode
     // clone before mutating since the root may be a hoisted vnode
-    root = cloneVNode(root)
+    root = cloneVNode(root, null, false, true)
     root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
     root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
   }
   }
   // inherit transition data
   // inherit transition data

+ 12 - 2
packages/runtime-core/src/vnode.ts

@@ -624,10 +624,11 @@ export function cloneVNode<T, U>(
   vnode: VNode<T, U>,
   vnode: VNode<T, U>,
   extraProps?: (Data & VNodeProps) | null,
   extraProps?: (Data & VNodeProps) | null,
   mergeRef = false,
   mergeRef = false,
+  cloneTransition = false,
 ): VNode<T, U> {
 ): VNode<T, U> {
   // This is intentionally NOT using spread or extend to avoid the runtime
   // This is intentionally NOT using spread or extend to avoid the runtime
   // key enumeration cost.
   // key enumeration cost.
-  const { props, ref, patchFlag, children } = vnode
+  const { props, ref, patchFlag, children, transition } = vnode
   const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
   const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
   const cloned: VNode<T, U> = {
   const cloned: VNode<T, U> = {
     __v_isVNode: true,
     __v_isVNode: true,
@@ -670,7 +671,7 @@ export function cloneVNode<T, U>(
     dynamicChildren: vnode.dynamicChildren,
     dynamicChildren: vnode.dynamicChildren,
     appContext: vnode.appContext,
     appContext: vnode.appContext,
     dirs: vnode.dirs,
     dirs: vnode.dirs,
-    transition: vnode.transition,
+    transition,
 
 
     // These should technically only be non-null on mounted VNodes. However,
     // These should technically only be non-null on mounted VNodes. However,
     // they *should* be copied for kept-alive vnodes. So we just always copy
     // they *should* be copied for kept-alive vnodes. So we just always copy
@@ -685,9 +686,18 @@ export function cloneVNode<T, U>(
     ctx: vnode.ctx,
     ctx: vnode.ctx,
     ce: vnode.ce,
     ce: vnode.ce,
   }
   }
+
+  // if the vnode will be replaced by the cloned one, it is necessary
+  // to clone the transition to ensure that the vnode referenced within
+  // the transition hooks is fresh.
+  if (transition && cloneTransition) {
+    cloned.transition = transition.clone(cloned as VNode)
+  }
+
   if (__COMPAT__) {
   if (__COMPAT__) {
     defineLegacyVNodeProperties(cloned as VNode)
     defineLegacyVNodeProperties(cloned as VNode)
   }
   }
+
   return cloned
   return cloned
 }
 }
 
 

+ 48 - 0
packages/vue/__tests__/e2e/Transition.spec.ts

@@ -1215,6 +1215,54 @@ describe('e2e: Transition', () => {
       E2E_TIMEOUT,
       E2E_TIMEOUT,
     )
     )
 
 
+    // #3716
+    test(
+      'wrapping transition + fallthrough attrs',
+      async () => {
+        await page().goto(baseUrl)
+        await page().waitForSelector('#app')
+        await page().evaluate(() => {
+          const { createApp, ref } = (window as any).Vue
+          createApp({
+            components: {
+              'my-transition': {
+                template: `
+                  <transition foo="1" name="test">
+                    <slot></slot>
+                  </transition>
+                `,
+              },
+            },
+            template: `
+            <div id="container">
+              <my-transition>
+                <div v-if="toggle">content</div>
+              </my-transition>
+            </div>
+            <button id="toggleBtn" @click="click">button</button>
+          `,
+            setup: () => {
+              const toggle = ref(true)
+              const click = () => (toggle.value = !toggle.value)
+              return { toggle, click }
+            },
+          }).mount('#app')
+        })
+        expect(await html('#container')).toBe('<div foo="1">content</div>')
+
+        await click('#toggleBtn')
+        // toggle again before leave finishes
+        await nextTick()
+        await click('#toggleBtn')
+
+        await transitionFinish()
+        expect(await html('#container')).toBe(
+          '<div foo="1" class="">content</div>',
+        )
+      },
+      E2E_TIMEOUT,
+    )
+
     test(
     test(
       'w/ KeepAlive + unmount innerChild',
       'w/ KeepAlive + unmount innerChild',
       async () => {
       async () => {