]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(keep-alive): include/exclude should work with async component (#3531)
authorHcySunYang <HcySunYang@outlook.com>
Tue, 25 May 2021 14:50:00 +0000 (22:50 +0800)
committerGitHub <noreply@github.com>
Tue, 25 May 2021 14:50:00 +0000 (10:50 -0400)
fix #3529

packages/runtime-core/__tests__/components/KeepAlive.spec.ts
packages/runtime-core/src/apiAsyncComponent.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/components/KeepAlive.ts

index 1cc7fe01effce8c33af4ea85ac021d7da4d94c48..062c0e0a8ba28745e43ab3dcf2c51865beb4f738 100644 (file)
@@ -14,10 +14,14 @@ import {
   ComponentPublicInstance,
   Ref,
   cloneVNode,
-  provide
+  provide,
+  defineAsyncComponent,
+  Component
 } from '@vue/runtime-test'
 import { KeepAliveProps } from '../../src/components/KeepAlive'
 
+const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
+
 describe('KeepAlive', () => {
   let one: ComponentOptions
   let two: ComponentOptions
@@ -823,4 +827,53 @@ describe('KeepAlive', () => {
     await nextTick()
     expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
   })
+
+  test('should work with async component', async () => {
+    let resolve: (comp: Component) => void
+    const AsyncComp = defineAsyncComponent(
+      () =>
+        new Promise(r => {
+          resolve = r as any
+        })
+    )
+
+    const toggle = ref(true)
+    const instanceRef = ref<any>(null)
+    const App = {
+      render: () => {
+        return h(
+          KeepAlive,
+          { include: 'Foo' },
+          () => (toggle.value ? h(AsyncComp, { ref: instanceRef }) : null)
+        )
+      }
+    }
+
+    render(h(App), root)
+    // async component has not been resolved
+    expect(serializeInner(root)).toBe('<!---->')
+
+    resolve!({
+      name: 'Foo',
+      data: () => ({ count: 0 }),
+      render() {
+        return h('p', this.count)
+      }
+    })
+
+    await timeout()
+    // resolved
+    expect(serializeInner(root)).toBe('<p>0</p>')
+
+    // change state + toggle out
+    instanceRef.value.count++
+    toggle.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe('<!---->')
+
+    // toggle in, state should be maintained
+    toggle.value = true
+    await nextTick()
+    expect(serializeInner(root)).toBe('<p>1</p>')
+  })
 })
index 4da00504329c0a44912cef1ed4f92e5252cd7fba..e705bf52a6a485ad4652835fe52b81a715770fd0 100644 (file)
@@ -13,6 +13,8 @@ import { defineComponent } from './apiDefineComponent'
 import { warn } from './warning'
 import { ref } from '@vue/reactivity'
 import { handleError, ErrorCodes } from './errorHandling'
+import { isKeepAlive } from './components/KeepAlive'
+import { queueJob } from './scheduler'
 
 export type AsyncComponentResolveResult<T = Component> = T | { default: T } // es modules
 
@@ -109,8 +111,14 @@ export function defineAsyncComponent<
   }
 
   return defineComponent({
-    __asyncLoader: load,
     name: 'AsyncComponentWrapper',
+
+    __asyncLoader: load,
+
+    get __asyncResolved() {
+      return resolvedComp
+    },
+
     setup() {
       const instance = currentInstance!
 
@@ -174,6 +182,11 @@ export function defineAsyncComponent<
       load()
         .then(() => {
           loaded.value = true
+          if (instance.parent && isKeepAlive(instance.parent.vnode)) {
+            // parent is keep-alive, force update so the loaded component's
+            // name is taken into account
+            queueJob(instance.parent.update)
+          }
         })
         .catch(err => {
           onError(err)
index 7c55033ce505ebc3557ee559a44e058df1fb0448..d281b3c51b1719901084cb196008b2a0e9213704 100644 (file)
@@ -189,6 +189,11 @@ export interface ComponentOptionsBase<
    * @internal
    */
   __asyncLoader?: () => Promise<ConcreteComponent>
+  /**
+   * the inner component resolved by the AsyncComponentWrapper
+   * @internal
+   */
+  __asyncResolved?: ConcreteComponent
   /**
    * cache for merged $options
    * @internal
index 9226b77523fefc3a6e05359b4b938cd8e2fd4612..a394acbda6b04e21ca64451010561c8b2af884f8 100644 (file)
@@ -36,6 +36,7 @@ import {
 import { setTransitionHooks } from './BaseTransition'
 import { ComponentRenderContext } from '../componentPublicInstance'
 import { devtoolsComponentAdded } from '../devtools'
+import { isAsyncWrapper } from '../apiAsyncComponent'
 
 type MatchPattern = string | RegExp | string[] | RegExp[]
 
@@ -257,7 +258,15 @@ const KeepAliveImpl: ComponentOptions = {
 
       let vnode = getInnerChild(rawVNode)
       const comp = vnode.type as ConcreteComponent
-      const name = getComponentName(comp)
+
+      // for async components, name check should be based in its loaded
+      // inner component if available
+      const name = getComponentName(
+        isAsyncWrapper(vnode)
+          ? (vnode.type as ComponentOptions).__asyncResolved || {}
+          : comp
+      )
+
       const { include, exclude, max } = props
 
       if (