From 9e3708ca754c0ecd66dbb45984f8d103772bd55c Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Tue, 25 May 2021 22:50:00 +0800 Subject: [PATCH] fix(keep-alive): include/exclude should work with async component (#3531) fix #3529 --- .../__tests__/components/KeepAlive.spec.ts | 55 ++++++++++++++++++- .../runtime-core/src/apiAsyncComponent.ts | 15 ++++- packages/runtime-core/src/componentOptions.ts | 5 ++ .../runtime-core/src/components/KeepAlive.ts | 11 +++- 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 1cc7fe01eff..062c0e0a8ba 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -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(`
changed
`) }) + + 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(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('

0

') + + // 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('

1

') + }) }) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 4da00504329..e705bf52a6a 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -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 | { 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) diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 7c55033ce50..d281b3c51b1 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -189,6 +189,11 @@ export interface ComponentOptionsBase< * @internal */ __asyncLoader?: () => Promise + /** + * the inner component resolved by the AsyncComponentWrapper + * @internal + */ + __asyncResolved?: ConcreteComponent /** * cache for merged $options * @internal diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 9226b77523f..a394acbda6b 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -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 (