From 376b42aed9fb94ef92a26777fadd69e9fb306638 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Fri, 2 Apr 2021 15:33:09 +0800 Subject: [PATCH 1/3] fix(KeepAlive): should work with async component --- .../__tests__/components/KeepAlive.spec.ts | 55 ++++++++++++++++++- .../runtime-core/src/apiAsyncComponent.ts | 9 +++ packages/runtime-core/src/componentOptions.ts | 5 ++ .../runtime-core/src/components/KeepAlive.ts | 8 ++- 4 files changed, 75 insertions(+), 2 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..1c61b2a5b7e 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 @@ -110,6 +112,9 @@ export function defineAsyncComponent< return defineComponent({ __asyncLoader: load, + get __resolvedComp() { + return resolvedComp + }, name: 'AsyncComponentWrapper', setup() { const instance = currentInstance! @@ -174,6 +179,10 @@ export function defineAsyncComponent< load() .then(() => { loaded.value = true + if (instance.parent && isKeepAlive(instance.parent.vnode)) { + // force update + 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 40f5669b508..4d3ea6a0c50 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -185,6 +185,11 @@ export interface ComponentOptionsBase< * @internal */ __asyncLoader?: () => Promise + /** + * the inner component resolved by the AsyncComponentWrapper + * @internal + */ + __resolvedComp?: 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..52b499e22d1 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,12 @@ const KeepAliveImpl: ComponentOptions = { let vnode = getInnerChild(rawVNode) const comp = vnode.type as ConcreteComponent - const name = getComponentName(comp) + const name = getComponentName( + isAsyncWrapper(vnode) + ? (vnode.type as ComponentOptions).__resolvedComp || {} + : comp + ) + const { include, exclude, max } = props if ( From 75ae9c6317648de3f5e4a49a551e0efa05641f1a Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 25 May 2021 10:43:11 -0400 Subject: [PATCH 2/3] chore: tweak option name --- packages/runtime-core/src/apiAsyncComponent.ts | 7 +++++-- packages/runtime-core/src/componentOptions.ts | 2 +- packages/runtime-core/src/components/KeepAlive.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 1c61b2a5b7e..5d8f42a66be 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -111,11 +111,14 @@ export function defineAsyncComponent< } return defineComponent({ + name: 'AsyncComponentWrapper', + __asyncLoader: load, - get __resolvedComp() { + + get __asyncResolved() { return resolvedComp }, - name: 'AsyncComponentWrapper', + setup() { const instance = currentInstance! diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 4d3ea6a0c50..be766b29555 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -189,7 +189,7 @@ export interface ComponentOptionsBase< * the inner component resolved by the AsyncComponentWrapper * @internal */ - __resolvedComp?: ConcreteComponent + __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 52b499e22d1..53279f3a4e5 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -260,7 +260,7 @@ const KeepAliveImpl: ComponentOptions = { const comp = vnode.type as ConcreteComponent const name = getComponentName( isAsyncWrapper(vnode) - ? (vnode.type as ComponentOptions).__resolvedComp || {} + ? (vnode.type as ComponentOptions).__asyncResolved || {} : comp ) From 6ab510a9b74510fc396123331ca94f52020f45fe Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 25 May 2021 10:47:08 -0400 Subject: [PATCH 3/3] chore: comments --- packages/runtime-core/src/apiAsyncComponent.ts | 3 ++- packages/runtime-core/src/components/KeepAlive.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 5d8f42a66be..e705bf52a6a 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -183,7 +183,8 @@ export function defineAsyncComponent< .then(() => { loaded.value = true if (instance.parent && isKeepAlive(instance.parent.vnode)) { - // force update + // parent is keep-alive, force update so the loaded component's + // name is taken into account queueJob(instance.parent.update) } }) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 53279f3a4e5..a394acbda6b 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -258,6 +258,9 @@ const KeepAliveImpl: ComponentOptions = { let vnode = getInnerChild(rawVNode) const comp = vnode.type as ConcreteComponent + + // 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 || {}