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