From 8a36d432116417716c99e753524d3fce59978dc7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 29 Oct 2024 18:30:18 +0800 Subject: [PATCH 1/4] fix(templateRef): set ref on async component which wrapped in KeepAlive --- .../__tests__/rendererTemplateRef.spec.ts | 64 +++++++++++++++++++ .../runtime-core/src/rendererTemplateRef.ts | 12 +++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts index 64cb9c63014..1cf486a179b 100644 --- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts @@ -1,4 +1,7 @@ import { + type Component, + KeepAlive, + defineAsyncComponent, defineComponent, h, nextTick, @@ -538,4 +541,65 @@ describe('api: template refs', () => { '
[object Object],[object Object]
', ) }) + + test('with async component which nested in KeepAlive', async () => { + let resolve: (comp: Component) => void + const AsyncComp = defineAsyncComponent( + () => + new Promise(r => { + resolve = r 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() + resolve!({ + setup(_, { expose }) { + expose({ + name: 'AsyncComp', + }) + return () => h('div') + }, + }) + + await new Promise(r => setTimeout(r, 0)) + 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..a173048ad18 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,16 @@ 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) + } + // when mounting async components, nothing needs to be done, // because the template ref is forwarded to inner component return From dc78053afad83e8019e5a790e0891fa4c766e076 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 29 Oct 2024 18:34:49 +0800 Subject: [PATCH 2/4] chore: update --- packages/runtime-core/src/rendererTemplateRef.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index a173048ad18..70f772d9daf 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -50,6 +50,7 @@ export function setRef( vnode.component!.subTree.component ) { setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree) + return } // when mounting async components, nothing needs to be done, From f56ed5e180cb8ba901d3195b27813a03c5c94d8f Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 30 Oct 2024 10:14:40 +0800 Subject: [PATCH 3/4] chore: update test --- .../__tests__/rendererTemplateRef.spec.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts index 1cf486a179b..a7ae7a06bfd 100644 --- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts @@ -1,5 +1,4 @@ import { - type Component, KeepAlive, defineAsyncComponent, defineComponent, @@ -543,12 +542,22 @@ describe('api: template refs', () => { }) test('with async component which nested in KeepAlive', async () => { - let resolve: (comp: Component) => void const AsyncComp = defineAsyncComponent( () => - new Promise(r => { - resolve = r as any - }), + new Promise(resolve => + setTimeout(() => + resolve( + defineComponent({ + setup(_, { expose }) { + expose({ + name: 'AsyncComp', + }) + return () => h('div') + }, + }) as any, + ), + ), + ), ) const Comp = defineComponent({ @@ -580,16 +589,9 @@ describe('api: template refs', () => { // switch to async component toggle.value = true await nextTick() - resolve!({ - setup(_, { expose }) { - expose({ - name: 'AsyncComp', - }) - return () => h('div') - }, - }) + expect(instanceRef.value).toBe(null) - await new Promise(r => setTimeout(r, 0)) + await new Promise(r => setTimeout(r)) expect(instanceRef.value.name).toBe('AsyncComp') // switch back to normal component From 26930ca03de1d34eecb53fdece12010408275344 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 15 Nov 2024 22:21:53 +0800 Subject: [PATCH 4/4] remove unnecessary return --- packages/runtime-core/src/rendererTemplateRef.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index 70f772d9daf..ca21030dc35 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -50,11 +50,10 @@ export function setRef( 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 + // otherwise, nothing needs to be done because the template ref + // is forwarded to inner component return }