diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts
index 64cb9c63014..a7ae7a06bfd 100644
--- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts
+++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts
@@ -1,4 +1,6 @@
import {
+ KeepAlive,
+ defineAsyncComponent,
defineComponent,
h,
nextTick,
@@ -538,4 +540,68 @@ describe('api: template refs', () => {
'
[object Object],[object Object]
',
)
})
+
+ test('with async component which nested in KeepAlive', async () => {
+ const AsyncComp = defineAsyncComponent(
+ () =>
+ new Promise(resolve =>
+ setTimeout(() =>
+ resolve(
+ defineComponent({
+ setup(_, { expose }) {
+ expose({
+ name: 'AsyncComp',
+ })
+ return () => h('div')
+ },
+ }) as any,
+ ),
+ ),
+ ),
+ )
+
+ const Comp = defineComponent({
+ setup(_, { expose }) {
+ expose({
+ name: 'Comp',
+ })
+ return () => h('div')
+ },
+ })
+
+ const toggle = ref(false)
+ const instanceRef = ref(null)
+
+ const App = {
+ render: () => {
+ return h(KeepAlive, () =>
+ toggle.value
+ ? h(AsyncComp, { ref: instanceRef })
+ : h(Comp, { ref: instanceRef }),
+ )
+ },
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+ expect(instanceRef.value.name).toBe('Comp')
+
+ // switch to async component
+ toggle.value = true
+ await nextTick()
+ expect(instanceRef.value).toBe(null)
+
+ await new Promise(r => setTimeout(r))
+ expect(instanceRef.value.name).toBe('AsyncComp')
+
+ // switch back to normal component
+ toggle.value = false
+ await nextTick()
+ expect(instanceRef.value.name).toBe('Comp')
+
+ // switch to async component again
+ toggle.value = true
+ await nextTick()
+ expect(instanceRef.value.name).toBe('AsyncComp')
+ })
})
diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts
index bffe1a25321..70f772d9daf 100644
--- a/packages/runtime-core/src/rendererTemplateRef.ts
+++ b/packages/runtime-core/src/rendererTemplateRef.ts
@@ -15,7 +15,7 @@ import { isRef, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import type { SchedulerJob } from './scheduler'
import { queuePostRenderEffect } from './renderer'
-import { getComponentPublicInstance } from './component'
+import { type ComponentOptions, getComponentPublicInstance } from './component'
import { knownTemplateRefs } from './helpers/useTemplateRef'
/**
@@ -42,6 +42,17 @@ export function setRef(
}
if (isAsyncWrapper(vnode) && !isUnmount) {
+ // #4999 if an async component already resolved and cached by KeepAlive,
+ // we need to set the ref to inner component
+ if (
+ vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
+ (vnode.type as ComponentOptions).__asyncResolved &&
+ vnode.component!.subTree.component
+ ) {
+ setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
+ return
+ }
+
// when mounting async components, nothing needs to be done,
// because the template ref is forwarded to inner component
return