fix(templateRef): set ref on cached async component which wrapped in KeepAlive (#12290)

close #4999
close #5004
This commit is contained in:
edison 2024-11-15 22:32:22 +08:00 committed by GitHub
parent da7ad5e3d2
commit 983eb50a17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 79 additions and 3 deletions

View File

@ -1,4 +1,6 @@
import {
KeepAlive,
defineAsyncComponent,
defineComponent,
h,
nextTick,
@ -538,4 +540,68 @@ describe('api: template refs', () => {
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
)
})
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<any>(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')
})
})

View File

@ -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,8 +42,18 @@ export function setRef(
}
if (isAsyncWrapper(vnode) && !isUnmount) {
// when mounting async components, nothing needs to be done,
// because the template ref is forwarded to inner component
// #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)
}
// otherwise, nothing needs to be done because the template ref
// is forwarded to inner component
return
}