mirror of https://github.com/vuejs/core.git
fix(runtime-core): properly handle inherit transition during clone VNode (#10809)
close #3716 close #10497 close #4091
This commit is contained in:
parent
e8fd6446d1
commit
638a79f64a
|
@ -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, {
|
root = cloneVNode(
|
||||||
|
root,
|
||||||
|
{
|
||||||
class: cls,
|
class: cls,
|
||||||
style: style,
|
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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
Loading…
Reference in New Issue