]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(TransitionGroup): reset prevChildren to prevent memory leak (#13183)
authoredison <daiwei521@126.com>
Thu, 1 May 2025 09:58:07 +0000 (17:58 +0800)
committerGitHub <noreply@github.com>
Thu, 1 May 2025 09:58:07 +0000 (02:58 -0700)
close #13181

packages/runtime-dom/src/components/TransitionGroup.ts
packages/vue/__tests__/e2e/TransitionGroup.spec.ts

index 928e5d955f11415736bd6691ba79afbed251acb4..7bb5ce0c6b13e6657928db879a455c897e30d116 100644 (file)
@@ -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 () => {
index 22f2254090263c20250d91735656dca9850c7064..62d89db4e310c23d1c5d41a21ac2cc10298c2875 100644 (file)
@@ -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: `
+              <p>{{ test.length }}</p>
+            `,
+            },
+          },
+          template: `
+          <transition-group>
+            <Child v-if="show" />
+          </transition-group>
+        `,
+          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,
+  )
 })