Skip to content

Commit 983eb50

Browse files
authoredNov 15, 2024··
fix(templateRef): set ref on cached async component which wrapped in KeepAlive (#12290)
close #4999 close #5004
1 parent da7ad5e commit 983eb50

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed
 

‎packages/runtime-core/__tests__/rendererTemplateRef.spec.ts

+66
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
KeepAlive,
3+
defineAsyncComponent,
24
defineComponent,
35
h,
46
nextTick,
@@ -538,4 +540,68 @@ describe('api: template refs', () => {
538540
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
539541
)
540542
})
543+
544+
test('with async component which nested in KeepAlive', async () => {
545+
const AsyncComp = defineAsyncComponent(
546+
() =>
547+
new Promise(resolve =>
548+
setTimeout(() =>
549+
resolve(
550+
defineComponent({
551+
setup(_, { expose }) {
552+
expose({
553+
name: 'AsyncComp',
554+
})
555+
return () => h('div')
556+
},
557+
}) as any,
558+
),
559+
),
560+
),
561+
)
562+
563+
const Comp = defineComponent({
564+
setup(_, { expose }) {
565+
expose({
566+
name: 'Comp',
567+
})
568+
return () => h('div')
569+
},
570+
})
571+
572+
const toggle = ref(false)
573+
const instanceRef = ref<any>(null)
574+
575+
const App = {
576+
render: () => {
577+
return h(KeepAlive, () =>
578+
toggle.value
579+
? h(AsyncComp, { ref: instanceRef })
580+
: h(Comp, { ref: instanceRef }),
581+
)
582+
},
583+
}
584+
585+
const root = nodeOps.createElement('div')
586+
render(h(App), root)
587+
expect(instanceRef.value.name).toBe('Comp')
588+
589+
// switch to async component
590+
toggle.value = true
591+
await nextTick()
592+
expect(instanceRef.value).toBe(null)
593+
594+
await new Promise(r => setTimeout(r))
595+
expect(instanceRef.value.name).toBe('AsyncComp')
596+
597+
// switch back to normal component
598+
toggle.value = false
599+
await nextTick()
600+
expect(instanceRef.value.name).toBe('Comp')
601+
602+
// switch to async component again
603+
toggle.value = true
604+
await nextTick()
605+
expect(instanceRef.value.name).toBe('AsyncComp')
606+
})
541607
})

‎packages/runtime-core/src/rendererTemplateRef.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { isRef, toRaw } from '@vue/reactivity'
1515
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
1616
import type { SchedulerJob } from './scheduler'
1717
import { queuePostRenderEffect } from './renderer'
18-
import { getComponentPublicInstance } from './component'
18+
import { type ComponentOptions, getComponentPublicInstance } from './component'
1919
import { knownTemplateRefs } from './helpers/useTemplateRef'
2020

2121
/**
@@ -42,8 +42,18 @@ export function setRef(
4242
}
4343

4444
if (isAsyncWrapper(vnode) && !isUnmount) {
45-
// when mounting async components, nothing needs to be done,
46-
// because the template ref is forwarded to inner component
45+
// #4999 if an async component already resolved and cached by KeepAlive,
46+
// we need to set the ref to inner component
47+
if (
48+
vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
49+
(vnode.type as ComponentOptions).__asyncResolved &&
50+
vnode.component!.subTree.component
51+
) {
52+
setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
53+
}
54+
55+
// otherwise, nothing needs to be done because the template ref
56+
// is forwarded to inner component
4757
return
4858
}
4959

0 commit comments

Comments
 (0)
Please sign in to comment.