Просмотр исходного кода

fix(runtime-core): fix async component ref handling (#3191)

fix #3188
HcySunYang 5 лет назад
Родитель
Сommit
7562e72c2b

+ 48 - 1
packages/runtime-core/__tests__/apiAsyncComponent.spec.ts

@@ -662,7 +662,7 @@ describe('api: defineAsyncComponent', () => {
         })
     )
 
-    const fooRef = ref()
+    const fooRef = ref<any>(null)
     const toggle = ref(true)
     const root = nodeOps.createElement('div')
     createApp({
@@ -697,4 +697,51 @@ describe('api: defineAsyncComponent', () => {
     expect(serializeInner(root)).toBe('resolved')
     expect(fooRef.value.id).toBe('foo')
   })
+
+  // #3188
+  test('the forwarded template ref should always exist when doing multi patching', async () => {
+    let resolve: (comp: Component) => void
+    const Foo = defineAsyncComponent(
+      () =>
+        new Promise(r => {
+          resolve = r as any
+        })
+    )
+
+    const fooRef = ref<any>(null)
+    const toggle = ref(true)
+    const updater = ref(0)
+
+    const root = nodeOps.createElement('div')
+    createApp({
+      render: () =>
+        toggle.value ? [h(Foo, { ref: fooRef }), updater.value] : null
+    }).mount(root)
+
+    expect(serializeInner(root)).toBe('<!---->0')
+    expect(fooRef.value).toBe(null)
+
+    resolve!({
+      data() {
+        return {
+          id: 'foo'
+        }
+      },
+      render: () => 'resolved'
+    })
+
+    await timeout()
+    expect(serializeInner(root)).toBe('resolved0')
+    expect(fooRef.value.id).toBe('foo')
+
+    updater.value++
+    await nextTick()
+    expect(serializeInner(root)).toBe('resolved1')
+    expect(fooRef.value.id).toBe('foo')
+
+    toggle.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe('<!---->')
+    expect(fooRef.value).toBe(null)
+  })
 })

+ 5 - 1
packages/runtime-core/src/renderer.ts

@@ -313,9 +313,13 @@ export const setRef = (
   }
 
   let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
-  if (!vnode || isAsyncWrapper(vnode)) {
+  if (!vnode) {
+    // means unmount
     value = null
   } else {
+    // when mounting async components, nothing needs to be done,
+    // because the template ref is forwarded to inner component
+    if (isAsyncWrapper(vnode)) return
     if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
       value = vnode.component!.exposed || vnode.component!.proxy
     } else {