Pārlūkot izejas kodu

fix(runtime-vapor): avoid leaking wrapper keys into keep-alive cache

daiwei 3 dienas atpakaļ
vecāks
revīzija
5920e7716f

+ 32 - 0
packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts

@@ -194,6 +194,38 @@ describe('VaporKeepAlive', () => {
     expect(root.innerHTML).toBe(`<div>changed</div><!--dynamic-component-->`)
   })
 
+  test('should not use wrapper key as child cache key', async () => {
+    const viewRef = ref('one')
+    let cache!: Map<any, any>
+
+    const { mount } = define({
+      setup() {
+        const n0 = createComponent(VaporKeepAlive, null, {
+          default: () => createDynamicComponent(() => views[viewRef.value]),
+        })
+        setBlockKey(n0, 'wrapper')
+        cache = (n0 as any).__v_cache
+        return n0
+      },
+    }).create()
+
+    mount(root)
+    await nextTick()
+    expect(cache.has(one)).toBe(true)
+    expect(cache.has('wrapper')).toBe(false)
+    expect(oneHooks.beforeMount).toHaveBeenCalledTimes(1)
+
+    viewRef.value = 'two'
+    await nextTick()
+    expect(cache.has(one)).toBe(true)
+    expect(cache.has(two)).toBe(true)
+    expect(cache.has('wrapper')).toBe(false)
+
+    viewRef.value = 'one'
+    await nextTick()
+    expect(oneHooks.beforeMount).toHaveBeenCalledTimes(1)
+  })
+
   test('should cache same component across branches', async () => {
     const toggle = ref(true)
     const instanceA = ref<any>(null)

+ 66 - 0
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -1021,6 +1021,72 @@ describe('Vapor Mode hydration', () => {
       )
     })
 
+    test('v-if KeepAlive with dynamic component should preserve cached branches', async () => {
+      const data = ref({
+        current: 'CompA',
+        useKeepAlive: true,
+      })
+      const { container } = await testHydration(
+        `<script setup>
+          import { KeepAlive, computed } from 'vue'
+          const data = _data
+          const components = _components
+          const current = computed(() => components[data.value.current])
+        </script>
+        <template>
+          <KeepAlive v-if="data.useKeepAlive">
+            <component :is="current" />
+          </KeepAlive>
+          <component v-else :is="current" />
+        </template>`,
+        {
+          CompA: `<script setup>
+            import { ref } from 'vue'
+            const count = ref(0)
+          </script>
+          <template>
+            <button @click="count++">A {{ count }}</button>
+          </template>`,
+          CompB: `<script setup>
+            import { ref } from 'vue'
+            const msg = ref('')
+          </script>
+          <template>
+            <input v-model="msg">
+            <p>B {{ msg }}</p>
+          </template>`,
+        },
+        data,
+      )
+
+      const getButton = () =>
+        container.querySelector('button') as HTMLButtonElement
+      const getInput = () =>
+        container.querySelector('input') as HTMLInputElement
+      const getText = () => container.querySelector('p')!.textContent
+
+      expect(getButton().textContent).toBe('A 0')
+      triggerEvent('click', getButton())
+      await nextTick()
+      expect(getButton().textContent).toBe('A 1')
+
+      data.value.current = 'CompB'
+      await nextTick()
+      getInput().value = 'hello'
+      triggerEvent('input', getInput())
+      await nextTick()
+      expect(getText()).toBe('B hello')
+
+      data.value.current = 'CompA'
+      await nextTick()
+      expect(getButton().textContent).toBe('A 1')
+
+      data.value.current = 'CompB'
+      await nextTick()
+      expect(getInput().value).toBe('hello')
+      expect(getText()).toBe('B hello')
+    })
+
     test('consecutive dynamic components with insertion anchor', async () => {
       const { container, data } = await testHydration(
         `<template>

+ 5 - 1
packages/runtime-vapor/src/helpers/setKey.ts

@@ -1,4 +1,5 @@
 import { isArray } from '@vue/shared'
+import { isKeepAlive } from '@vue/runtime-dom'
 import type { Block } from '../block'
 import { isVaporComponent } from '../component'
 
@@ -12,7 +13,10 @@ export function setBlockKey(
     block.$key = key
   } else if (isVaporComponent(block)) {
     block.$key = key
-    if (block.block) setBlockKey(block.block, key)
+    // KeepAlive resolves cache keys from its child block. An outer wrapper key
+    // (for example from v-if) must not override the child's own component type
+    // or explicit key, otherwise cached branches will not be found again.
+    if (!isKeepAlive(block) && block.block) setBlockKey(block.block, key)
   } else if (isArray(block)) {
     if (block.length === 1) {
       setBlockKey(block[0], key)