diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 4dce5d144cf..08c9b94e6ca 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -874,4 +874,43 @@ describe('KeepAlive', () => { await nextTick() expect(serializeInner(root)).toBe('

1

') }) + + test('should correctly set ref 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, () => + toggle.value ? h(AsyncComp, { ref: instanceRef }) : null + ) + } + } + + render(h(App), root) + + resolve!({ + render() { + return h('div') + } + }) + + await timeout() + expect(instanceRef.value).not.toBe(null) + + toggle.value = false + await nextTick() + + toggle.value = true + await nextTick() + + expect(instanceRef.value).not.toBe(null) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 7cf8b49521f..ef4ecf027a4 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -477,7 +477,7 @@ function baseCreateRenderer( // set ref if (ref != null && parentComponent) { - setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2) + setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2, parentComponent) } } @@ -2355,7 +2355,8 @@ export function setRef( oldRawRef: VNodeNormalizedRef | null, parentSuspense: SuspenseBoundary | null, vnode: VNode, - isUnmount = false + isUnmount = false, + parentComponent: ComponentInternalInstance | null = null ) { if (isArray(rawRef)) { rawRef.forEach((r, i) => @@ -2370,7 +2371,15 @@ export function setRef( return } - if (isAsyncWrapper(vnode) && !isUnmount) { + if ( + isAsyncWrapper(vnode) && + !isUnmount && + // #4999 + // when async-component in keep-alive, it should set ref to async-component + // so actual vnode in async-component can access right ref, because the + // keep-alive cache the wrapper vnode + !(parentComponent && isKeepAlive(parentComponent.vnode)) + ) { // when mounting async components, nothing needs to be done, // because the template ref is forwarded to inner component return