diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts index 928e5d955f1..7bb5ce0c6b1 100644 --- a/packages/runtime-dom/src/components/TransitionGroup.ts +++ b/packages/runtime-dom/src/components/TransitionGroup.ts @@ -81,6 +81,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({ moveClass, ) ) { + prevChildren = [] return } @@ -110,6 +111,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({ }) el.addEventListener('transitionend', cb) }) + prevChildren = [] }) return () => { diff --git a/packages/vue/__tests__/e2e/TransitionGroup.spec.ts b/packages/vue/__tests__/e2e/TransitionGroup.spec.ts index 22f22540902..62d89db4e31 100644 --- a/packages/vue/__tests__/e2e/TransitionGroup.spec.ts +++ b/packages/vue/__tests__/e2e/TransitionGroup.spec.ts @@ -645,4 +645,55 @@ describe('e2e: TransitionGroup', () => { }, E2E_TIMEOUT, ) + + test( + 'not leaking after children unmounted', + async () => { + const client = await page().createCDPSession() + await page().evaluate(async () => { + const { createApp, ref, nextTick } = (window as any).Vue + const show = ref(true) + + createApp({ + components: { + Child: { + setup: () => { + // Big arrays kick GC earlier + const test = ref([...Array(3000)].map((_, i) => ({ i }))) + // @ts-expect-error - Custom property and same lib as runtime is used + window.__REF__ = new WeakRef(test) + + return { test } + }, + template: ` +

{{ test.length }}

+ `, + }, + }, + template: ` + + + + `, + setup() { + return { show } + }, + }).mount('#app') + + show.value = false + await nextTick() + }) + + const isCollected = async () => + // @ts-expect-error - Custom property + await page().evaluate(() => window.__REF__.deref() === undefined) + + while ((await isCollected()) === false) { + await client.send('HeapProfiler.collectGarbage') + } + + expect(await isCollected()).toBe(true) + }, + E2E_TIMEOUT, + ) })