Browse Source

fix(runtime-vapor): handle async component names in KeepAlive pruneCache

daiwei 2 months ago
parent
commit
b210f010b8

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

@@ -1541,6 +1541,111 @@ describe('VaporKeepAlive', () => {
     expect(activated).toHaveBeenCalledTimes(0)
   })
 
+  test('should not prune cached async component when its resolved name still matches include', async () => {
+    let resolve: (comp: VaporComponent) => void
+    const AsyncComp = defineVaporAsyncComponent(
+      () =>
+        new Promise(r => {
+          resolve = r as any
+        }),
+    )
+
+    const include = ref('Foo')
+    const toggle = ref(true)
+    const { html, instance } = define({
+      setup() {
+        return createComponent(
+          VaporKeepAlive,
+          { include: () => include.value },
+          {
+            default: () => {
+              return createIf(
+                () => toggle.value,
+                () => createComponent(AsyncComp),
+              )
+            },
+          },
+        )
+      },
+    }).render()
+
+    resolve!(
+      defineVaporComponent({
+        name: 'Foo',
+        setup() {
+          return template(`<div>Foo</div>`)()
+        },
+      }),
+    )
+    await timeout()
+    expect(html()).toBe(`<div>Foo</div><!--async component--><!--if-->`)
+
+    toggle.value = false
+    await nextTick()
+    expect(html()).toBe('<!--if-->')
+
+    const keepAliveInstance = instance!.block as any
+    const cache = keepAliveInstance.__v_cache as Map<any, any>
+    expect(cache.size).toBe(1)
+
+    include.value = 'Foo,Bar'
+    await nextTick()
+    expect(cache.size).toBe(1)
+  })
+
+  test('should prune cached async component when its resolved name no longer matches include', async () => {
+    let resolve: (comp: VaporComponent) => void
+    const AsyncComp = defineVaporAsyncComponent(
+      () =>
+        new Promise(r => {
+          resolve = r as any
+        }),
+    )
+
+    const include = ref('Foo')
+    const toggle = ref(true)
+    const { html, instance } = define({
+      setup() {
+        return createComponent(
+          VaporKeepAlive,
+          { include: () => include.value },
+          {
+            default: () => {
+              return createIf(
+                () => toggle.value,
+                () => createComponent(AsyncComp),
+              )
+            },
+          },
+        )
+      },
+    }).render()
+
+    resolve!(
+      defineVaporComponent({
+        name: 'Foo',
+        setup() {
+          return template(`<div>Foo</div>`)()
+        },
+      }),
+    )
+    await timeout()
+    expect(html()).toBe(`<div>Foo</div><!--async component--><!--if-->`)
+
+    toggle.value = false
+    await nextTick()
+    expect(html()).toBe('<!--if-->')
+
+    const keepAliveInstance = instance!.block as any
+    const cache = keepAliveInstance.__v_cache as Map<any, any>
+    expect(cache.size).toBe(1)
+
+    // 'Foo' no longer matches include 'Bar', should be pruned
+    include.value = 'Bar'
+    await nextTick()
+    expect(cache.size).toBe(0)
+  })
+
   test('handle error in async onActivated', async () => {
     const err = new Error('foo')
     const handler = vi.fn()

+ 2 - 1
packages/runtime-vapor/src/component.ts

@@ -924,7 +924,8 @@ export function unmountComponent(
   if (
     instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
     instance.parent &&
-    instance.parent.vapor
+    instance.parent.vapor &&
+    (instance.parent as KeepAliveInstance).ctx
   ) {
     if (parentNode) {
       ;(instance.parent as KeepAliveInstance)!.ctx.deactivate(instance)

+ 13 - 1
packages/runtime-vapor/src/components/KeepAlive.ts

@@ -304,7 +304,11 @@ const VaporKeepAliveImpl = defineVaporComponent({
       cache.forEach((cached, key) => {
         const instance = getInstanceFromCache(cached)
         if (!instance) return
-        const name = getComponentName(instance.type)
+        const name = getComponentName(
+          isAsyncWrapper(instance)
+            ? (instance.type as any).__asyncResolved || {}
+            : instance.type,
+        )
         if (name && !filter(name)) {
           pruneCacheEntry(key)
         }
@@ -441,6 +445,14 @@ const resetCachedShapeFlag = (
 ) => {
   if (isVaporComponent(cached)) {
     resetShapeFlag(cached)
+    // for async components, also reset the inner resolved component's
+    // shapeFlag.
+    if (isAsyncWrapper(cached)) {
+      const [inner] = getInnerBlock(cached.block)
+      if (inner && isVaporComponent(inner)) {
+        resetShapeFlag(inner)
+      }
+    }
   } else if (isInteropEnabled) {
     resetShapeFlag(cached.vnode)
   }