Skip to content

Commit 671cf29

Browse files
authoredJun 4, 2024··
fix(transition): ensure Transition enterHooks are updated after clone (#11066)
close #11061
1 parent ef2e737 commit 671cf29

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed
 

‎packages/runtime-core/src/components/BaseTransition.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,13 @@ const BaseTransitionImpl: ComponentOptions = {
203203
return emptyPlaceholder(child)
204204
}
205205

206-
const enterHooks = resolveTransitionHooks(
206+
let enterHooks = resolveTransitionHooks(
207207
innerChild,
208208
rawProps,
209209
state,
210210
instance,
211+
// #11061, ensure enterHooks is fresh after clone
212+
hooks => (enterHooks = hooks),
211213
)
212214
setTransitionHooks(innerChild, enterHooks)
213215

@@ -305,6 +307,7 @@ export function resolveTransitionHooks(
305307
props: BaseTransitionProps<any>,
306308
state: TransitionState,
307309
instance: ComponentInternalInstance,
310+
postClone?: (hooks: TransitionHooks) => void,
308311
): TransitionHooks {
309312
const {
310313
appear,
@@ -445,7 +448,15 @@ export function resolveTransitionHooks(
445448
},
446449

447450
clone(vnode) {
448-
return resolveTransitionHooks(vnode, props, state, instance)
451+
const hooks = resolveTransitionHooks(
452+
vnode,
453+
props,
454+
state,
455+
instance,
456+
postClone,
457+
)
458+
if (postClone) postClone(hooks)
459+
return hooks
449460
},
450461
}
451462

‎packages/runtime-core/src/vnode.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ import {
3636
isSuspense,
3737
} from './components/Suspense'
3838
import type { DirectiveBinding } from './directives'
39-
import type { TransitionHooks } from './components/BaseTransition'
39+
import {
40+
type TransitionHooks,
41+
setTransitionHooks,
42+
} from './components/BaseTransition'
4043
import { warn } from './warning'
4144
import {
4245
type Teleport,
@@ -691,7 +694,10 @@ export function cloneVNode<T, U>(
691694
// to clone the transition to ensure that the vnode referenced within
692695
// the transition hooks is fresh.
693696
if (transition && cloneTransition) {
694-
cloned.transition = transition.clone(cloned as VNode)
697+
setTransitionHooks(
698+
cloned as VNode,
699+
transition.clone(cloned as VNode) as TransitionHooks,
700+
)
695701
}
696702

697703
if (__COMPAT__) {

‎packages/vue/__tests__/e2e/Transition.spec.ts

+92
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,98 @@ describe('e2e: Transition', () => {
13401340
E2E_TIMEOUT,
13411341
)
13421342

1343+
// #11061
1344+
test(
1345+
'transition + fallthrough attrs (in-out mode)',
1346+
async () => {
1347+
const beforeLeaveSpy = vi.fn()
1348+
const onLeaveSpy = vi.fn()
1349+
const afterLeaveSpy = vi.fn()
1350+
const beforeEnterSpy = vi.fn()
1351+
const onEnterSpy = vi.fn()
1352+
const afterEnterSpy = vi.fn()
1353+
1354+
await page().exposeFunction('onLeaveSpy', onLeaveSpy)
1355+
await page().exposeFunction('onEnterSpy', onEnterSpy)
1356+
await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
1357+
await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
1358+
await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
1359+
await page().exposeFunction('afterEnterSpy', afterEnterSpy)
1360+
1361+
await page().evaluate(() => {
1362+
const { onEnterSpy, onLeaveSpy } = window as any
1363+
const { createApp, ref } = (window as any).Vue
1364+
createApp({
1365+
components: {
1366+
one: {
1367+
template: '<div>one</div>',
1368+
},
1369+
two: {
1370+
template: '<div>two</div>',
1371+
},
1372+
},
1373+
template: `
1374+
<div id="container">
1375+
<transition foo="1" name="test" mode="in-out"
1376+
@before-enter="beforeEnterSpy()"
1377+
@enter="onEnterSpy()"
1378+
@after-enter="afterEnterSpy()"
1379+
@before-leave="beforeLeaveSpy()"
1380+
@leave="onLeaveSpy()"
1381+
@after-leave="afterLeaveSpy()">
1382+
<component :is="view"></component>
1383+
</transition>
1384+
</div>
1385+
<button id="toggleBtn" @click="click">button</button>
1386+
`,
1387+
setup: () => {
1388+
const view = ref('one')
1389+
const click = () =>
1390+
(view.value = view.value === 'one' ? 'two' : 'one')
1391+
return {
1392+
view,
1393+
click,
1394+
beforeEnterSpy,
1395+
onEnterSpy,
1396+
afterEnterSpy,
1397+
beforeLeaveSpy,
1398+
onLeaveSpy,
1399+
afterLeaveSpy,
1400+
}
1401+
},
1402+
}).mount('#app')
1403+
})
1404+
expect(await html('#container')).toBe('<div foo="1">one</div>')
1405+
1406+
// toggle
1407+
await click('#toggleBtn')
1408+
await nextTick()
1409+
await transitionFinish()
1410+
expect(beforeEnterSpy).toBeCalledTimes(1)
1411+
expect(onEnterSpy).toBeCalledTimes(1)
1412+
expect(afterEnterSpy).toBeCalledTimes(1)
1413+
expect(beforeLeaveSpy).toBeCalledTimes(1)
1414+
expect(onLeaveSpy).toBeCalledTimes(1)
1415+
expect(afterLeaveSpy).toBeCalledTimes(1)
1416+
1417+
expect(await html('#container')).toBe('<div foo="1" class="">two</div>')
1418+
1419+
// toggle back
1420+
await click('#toggleBtn')
1421+
await nextTick()
1422+
await transitionFinish()
1423+
expect(beforeEnterSpy).toBeCalledTimes(2)
1424+
expect(onEnterSpy).toBeCalledTimes(2)
1425+
expect(afterEnterSpy).toBeCalledTimes(2)
1426+
expect(beforeLeaveSpy).toBeCalledTimes(2)
1427+
expect(onLeaveSpy).toBeCalledTimes(2)
1428+
expect(afterLeaveSpy).toBeCalledTimes(2)
1429+
1430+
expect(await html('#container')).toBe('<div foo="1" class="">one</div>')
1431+
},
1432+
E2E_TIMEOUT,
1433+
)
1434+
13431435
test(
13441436
'w/ KeepAlive + unmount innerChild',
13451437
async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.