]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(templateRef): set ref on cached async component which wrapped in KeepAlive (...
authoredison <daiwei521@126.com>
Fri, 15 Nov 2024 14:32:22 +0000 (22:32 +0800)
committerGitHub <noreply@github.com>
Fri, 15 Nov 2024 14:32:22 +0000 (22:32 +0800)
close #4999
close #5004

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

index 64cb9c63014a2295f67833f193f7caad8d55299e..a7ae7a06bfd4f30f21957325a7f4549170d174fc 100644 (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')
+  })
 })
index bffe1a25321220c6df2d483a45479041d98f9e2c..ca21030dc35d851a7c293e4d1dea84b3c8eacc7d 100644 (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
   }