Skip to content

Commit 140a768

Browse files
authored
fix(TransitionGroup): avoid set transition hooks for comment nodes and text nodes (#9421)
close #4621 close #4622 close #5153 close #5168 close #7898 close #9067
1 parent c4684d3 commit 140a768

File tree

2 files changed

+145
-12
lines changed

2 files changed

+145
-12
lines changed

Diff for: packages/runtime-dom/src/components/TransitionGroup.ts

+23-12
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,29 @@ const TransitionGroupImpl: ComponentOptions = {
112112
tag = 'span'
113113
}
114114

115-
prevChildren = children
115+
prevChildren = []
116+
if (children) {
117+
for (let i = 0; i < children.length; i++) {
118+
const child = children[i]
119+
if (child.el && child.el instanceof Element) {
120+
prevChildren.push(child)
121+
setTransitionHooks(
122+
child,
123+
resolveTransitionHooks(
124+
child,
125+
cssTransitionProps,
126+
state,
127+
instance,
128+
),
129+
)
130+
positionMap.set(
131+
child,
132+
(child.el as Element).getBoundingClientRect(),
133+
)
134+
}
135+
}
136+
}
137+
116138
children = slots.default ? getTransitionRawChildren(slots.default()) : []
117139

118140
for (let i = 0; i < children.length; i++) {
@@ -127,17 +149,6 @@ const TransitionGroupImpl: ComponentOptions = {
127149
}
128150
}
129151

130-
if (prevChildren) {
131-
for (let i = 0; i < prevChildren.length; i++) {
132-
const child = prevChildren[i]
133-
setTransitionHooks(
134-
child,
135-
resolveTransitionHooks(child, cssTransitionProps, state, instance),
136-
)
137-
positionMap.set(child, (child.el as Element).getBoundingClientRect())
138-
}
139-
}
140-
141152
return createVNode(tag, null, children)
142153
}
143154
},

Diff for: packages/vue/__tests__/e2e/TransitionGroup.spec.ts

+122
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,126 @@ describe('e2e: TransitionGroup', () => {
508508

509509
expect(`<TransitionGroup> children must be keyed`).toHaveBeenWarned()
510510
})
511+
512+
// #5168, #7898, #9067
513+
test(
514+
'avoid set transition hooks for comment node',
515+
async () => {
516+
await page().evaluate(duration => {
517+
const { createApp, ref, h, createCommentVNode } = (window as any).Vue
518+
519+
const show = ref(false)
520+
createApp({
521+
template: `
522+
<div id="container">
523+
<transition-group name="test">
524+
<div v-for="item in items" :key="item" class="test">{{item}}</div>
525+
<Child key="child"/>
526+
</transition-group>
527+
</div>
528+
<button id="toggleBtn" @click="click">button</button>
529+
`,
530+
components: {
531+
Child: {
532+
setup() {
533+
return () =>
534+
show.value
535+
? h('div', { class: 'test' }, 'child')
536+
: createCommentVNode('v-if', true)
537+
},
538+
},
539+
},
540+
setup: () => {
541+
const items = ref([])
542+
const click = () => {
543+
items.value = ['a', 'b', 'c']
544+
setTimeout(() => {
545+
show.value = true
546+
}, duration)
547+
}
548+
return { click, items }
549+
},
550+
}).mount('#app')
551+
}, duration)
552+
553+
expect(await html('#container')).toBe(`<!--v-if-->`)
554+
555+
expect(await htmlWhenTransitionStart()).toBe(
556+
`<div class="test test-enter-from test-enter-active">a</div>` +
557+
`<div class="test test-enter-from test-enter-active">b</div>` +
558+
`<div class="test test-enter-from test-enter-active">c</div>` +
559+
`<!--v-if-->`,
560+
)
561+
562+
await transitionFinish(duration)
563+
await nextFrame()
564+
expect(await html('#container')).toBe(
565+
`<div class="test">a</div>` +
566+
`<div class="test">b</div>` +
567+
`<div class="test">c</div>` +
568+
`<div class="test test-enter-active test-enter-to">child</div>`,
569+
)
570+
571+
await transitionFinish(duration)
572+
expect(await html('#container')).toBe(
573+
`<div class="test">a</div>` +
574+
`<div class="test">b</div>` +
575+
`<div class="test">c</div>` +
576+
`<div class="test">child</div>`,
577+
)
578+
},
579+
E2E_TIMEOUT,
580+
)
581+
582+
// #4621, #4622, #5153
583+
test(
584+
'avoid set transition hooks for text node',
585+
async () => {
586+
await page().evaluate(() => {
587+
const { createApp, ref } = (window as any).Vue
588+
const app = createApp({
589+
template: `
590+
<div id="container">
591+
<transition-group name="test">
592+
<div class="test">foo</div>
593+
<div class="test" v-if="show">bar</div>
594+
</transition-group>
595+
</div>
596+
<button id="toggleBtn" @click="click">button</button>
597+
`,
598+
setup: () => {
599+
const show = ref(false)
600+
const click = () => {
601+
show.value = true
602+
}
603+
return { show, click }
604+
},
605+
})
606+
607+
app.config.compilerOptions.whitespace = 'preserve'
608+
app.mount('#app')
609+
})
610+
611+
expect(await html('#container')).toBe(`<div class="test">foo</div>` + ` `)
612+
613+
expect(await htmlWhenTransitionStart()).toBe(
614+
`<div class="test">foo</div>` +
615+
` ` +
616+
`<div class="test test-enter-from test-enter-active">bar</div>`,
617+
)
618+
619+
await nextFrame()
620+
expect(await html('#container')).toBe(
621+
`<div class="test">foo</div>` +
622+
` ` +
623+
`<div class="test test-enter-active test-enter-to">bar</div>`,
624+
)
625+
626+
await transitionFinish(duration)
627+
expect(await html('#container')).toBe(
628+
`<div class="test">foo</div>` + ` ` + `<div class="test">bar</div>`,
629+
)
630+
},
631+
E2E_TIMEOUT,
632+
)
511633
})

0 commit comments

Comments
 (0)