mirror of https://github.com/vuejs/core.git
fix(transition): ensure Transition enterHooks are updated after clone (#11066)
close #11061
This commit is contained in:
parent
ef2e737577
commit
671cf297a5
|
@ -203,11 +203,13 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
return emptyPlaceholder(child)
|
||||
}
|
||||
|
||||
const enterHooks = resolveTransitionHooks(
|
||||
let enterHooks = resolveTransitionHooks(
|
||||
innerChild,
|
||||
rawProps,
|
||||
state,
|
||||
instance,
|
||||
// #11061, ensure enterHooks is fresh after clone
|
||||
hooks => (enterHooks = hooks),
|
||||
)
|
||||
setTransitionHooks(innerChild, enterHooks)
|
||||
|
||||
|
@ -305,6 +307,7 @@ export function resolveTransitionHooks(
|
|||
props: BaseTransitionProps<any>,
|
||||
state: TransitionState,
|
||||
instance: ComponentInternalInstance,
|
||||
postClone?: (hooks: TransitionHooks) => void,
|
||||
): TransitionHooks {
|
||||
const {
|
||||
appear,
|
||||
|
@ -445,7 +448,15 @@ export function resolveTransitionHooks(
|
|||
},
|
||||
|
||||
clone(vnode) {
|
||||
return resolveTransitionHooks(vnode, props, state, instance)
|
||||
const hooks = resolveTransitionHooks(
|
||||
vnode,
|
||||
props,
|
||||
state,
|
||||
instance,
|
||||
postClone,
|
||||
)
|
||||
if (postClone) postClone(hooks)
|
||||
return hooks
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,10 @@ import {
|
|||
isSuspense,
|
||||
} from './components/Suspense'
|
||||
import type { DirectiveBinding } from './directives'
|
||||
import type { TransitionHooks } from './components/BaseTransition'
|
||||
import {
|
||||
type TransitionHooks,
|
||||
setTransitionHooks,
|
||||
} from './components/BaseTransition'
|
||||
import { warn } from './warning'
|
||||
import {
|
||||
type Teleport,
|
||||
|
@ -691,7 +694,10 @@ export function cloneVNode<T, U>(
|
|||
// 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)
|
||||
setTransitionHooks(
|
||||
cloned as VNode,
|
||||
transition.clone(cloned as VNode) as TransitionHooks,
|
||||
)
|
||||
}
|
||||
|
||||
if (__COMPAT__) {
|
||||
|
|
|
@ -1340,6 +1340,98 @@ describe('e2e: Transition', () => {
|
|||
E2E_TIMEOUT,
|
||||
)
|
||||
|
||||
// #11061
|
||||
test(
|
||||
'transition + fallthrough attrs (in-out mode)',
|
||||
async () => {
|
||||
const beforeLeaveSpy = vi.fn()
|
||||
const onLeaveSpy = vi.fn()
|
||||
const afterLeaveSpy = vi.fn()
|
||||
const beforeEnterSpy = vi.fn()
|
||||
const onEnterSpy = vi.fn()
|
||||
const afterEnterSpy = vi.fn()
|
||||
|
||||
await page().exposeFunction('onLeaveSpy', onLeaveSpy)
|
||||
await page().exposeFunction('onEnterSpy', onEnterSpy)
|
||||
await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
|
||||
await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
|
||||
await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
|
||||
await page().exposeFunction('afterEnterSpy', afterEnterSpy)
|
||||
|
||||
await page().evaluate(() => {
|
||||
const { onEnterSpy, onLeaveSpy } = window as any
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
createApp({
|
||||
components: {
|
||||
one: {
|
||||
template: '<div>one</div>',
|
||||
},
|
||||
two: {
|
||||
template: '<div>two</div>',
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div id="container">
|
||||
<transition foo="1" name="test" mode="in-out"
|
||||
@before-enter="beforeEnterSpy()"
|
||||
@enter="onEnterSpy()"
|
||||
@after-enter="afterEnterSpy()"
|
||||
@before-leave="beforeLeaveSpy()"
|
||||
@leave="onLeaveSpy()"
|
||||
@after-leave="afterLeaveSpy()">
|
||||
<component :is="view"></component>
|
||||
</transition>
|
||||
</div>
|
||||
<button id="toggleBtn" @click="click">button</button>
|
||||
`,
|
||||
setup: () => {
|
||||
const view = ref('one')
|
||||
const click = () =>
|
||||
(view.value = view.value === 'one' ? 'two' : 'one')
|
||||
return {
|
||||
view,
|
||||
click,
|
||||
beforeEnterSpy,
|
||||
onEnterSpy,
|
||||
afterEnterSpy,
|
||||
beforeLeaveSpy,
|
||||
onLeaveSpy,
|
||||
afterLeaveSpy,
|
||||
}
|
||||
},
|
||||
}).mount('#app')
|
||||
})
|
||||
expect(await html('#container')).toBe('<div foo="1">one</div>')
|
||||
|
||||
// toggle
|
||||
await click('#toggleBtn')
|
||||
await nextTick()
|
||||
await transitionFinish()
|
||||
expect(beforeEnterSpy).toBeCalledTimes(1)
|
||||
expect(onEnterSpy).toBeCalledTimes(1)
|
||||
expect(afterEnterSpy).toBeCalledTimes(1)
|
||||
expect(beforeLeaveSpy).toBeCalledTimes(1)
|
||||
expect(onLeaveSpy).toBeCalledTimes(1)
|
||||
expect(afterLeaveSpy).toBeCalledTimes(1)
|
||||
|
||||
expect(await html('#container')).toBe('<div foo="1" class="">two</div>')
|
||||
|
||||
// toggle back
|
||||
await click('#toggleBtn')
|
||||
await nextTick()
|
||||
await transitionFinish()
|
||||
expect(beforeEnterSpy).toBeCalledTimes(2)
|
||||
expect(onEnterSpy).toBeCalledTimes(2)
|
||||
expect(afterEnterSpy).toBeCalledTimes(2)
|
||||
expect(beforeLeaveSpy).toBeCalledTimes(2)
|
||||
expect(onLeaveSpy).toBeCalledTimes(2)
|
||||
expect(afterLeaveSpy).toBeCalledTimes(2)
|
||||
|
||||
expect(await html('#container')).toBe('<div foo="1" class="">one</div>')
|
||||
},
|
||||
E2E_TIMEOUT,
|
||||
)
|
||||
|
||||
test(
|
||||
'w/ KeepAlive + unmount innerChild',
|
||||
async () => {
|
||||
|
|
Loading…
Reference in New Issue