]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): vnode hooks should not be called on async wrapper (#4349)
authorHcySunYang <HcySunYang@outlook.com>
Mon, 16 Aug 2021 19:35:50 +0000 (03:35 +0800)
committerGitHub <noreply@github.com>
Mon, 16 Aug 2021 19:35:50 +0000 (15:35 -0400)
fix #4346

packages/runtime-core/__tests__/apiAsyncComponent.spec.ts
packages/runtime-core/src/renderer.ts

index 6f16aa8d3037e6de8870c2211096da3afeb55838..b771ba9652de8141409cf0810742395829087cdf 100644 (file)
@@ -744,4 +744,59 @@ describe('api: defineAsyncComponent', () => {
     expect(serializeInner(root)).toBe('<!---->')
     expect(fooRef.value).toBe(null)
   })
+
+  test('vnode hooks on async wrapper', async () => {
+    let resolve: (comp: Component) => void
+    const Foo = defineAsyncComponent(
+      () =>
+        new Promise(r => {
+          resolve = r as any
+        })
+    )
+    const updater = ref(0)
+
+    const vnodeHooks = {
+      onVnodeBeforeMount: jest.fn(),
+      onVnodeMounted: jest.fn(),
+      onVnodeBeforeUpdate: jest.fn(),
+      onVnodeUpdated: jest.fn(),
+      onVnodeBeforeUnmount: jest.fn(),
+      onVnodeUnmounted: jest.fn()
+    }
+
+    const toggle = ref(true)
+
+    const root = nodeOps.createElement('div')
+    createApp({
+      render: () => (toggle.value ? [h(Foo, vnodeHooks), updater.value] : null)
+    }).mount(root)
+
+    expect(serializeInner(root)).toBe('<!---->0')
+
+    resolve!({
+      data() {
+        return {
+          id: 'foo'
+        }
+      },
+      render: () => 'resolved'
+    })
+
+    await timeout()
+    expect(serializeInner(root)).toBe('resolved0')
+    expect(vnodeHooks.onVnodeBeforeMount).toHaveBeenCalledTimes(1)
+    expect(vnodeHooks.onVnodeMounted).toHaveBeenCalledTimes(1)
+
+    updater.value++
+    await nextTick()
+    expect(serializeInner(root)).toBe('resolved1')
+    expect(vnodeHooks.onVnodeBeforeUpdate).toHaveBeenCalledTimes(1)
+    expect(vnodeHooks.onVnodeUpdated).toHaveBeenCalledTimes(1)
+
+    toggle.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe('<!---->')
+    expect(vnodeHooks.onVnodeBeforeUnmount).toHaveBeenCalledTimes(1)
+    expect(vnodeHooks.onVnodeUnmounted).toHaveBeenCalledTimes(1)
+  })
 })
index f1f2f95214dffc200b775478d2001876302f60a6..fb7e6a46c85f5a8f817bd6b753538bf61da654db 100644 (file)
@@ -1316,6 +1316,7 @@ function baseCreateRenderer(
         let vnodeHook: VNodeHook | null | undefined
         const { el, props } = initialVNode
         const { bm, m, parent } = instance
+        const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
 
         effect.allowRecurse = false
         // beforeMount hook
@@ -1323,7 +1324,10 @@ function baseCreateRenderer(
           invokeArrayFns(bm)
         }
         // onVnodeBeforeMount
-        if ((vnodeHook = props && props.onVnodeBeforeMount)) {
+        if (
+          !isAsyncWrapperVNode &&
+          (vnodeHook = props && props.onVnodeBeforeMount)
+        ) {
           invokeVNodeHook(vnodeHook, parent, initialVNode)
         }
         if (
@@ -1359,7 +1363,7 @@ function baseCreateRenderer(
             }
           }
 
-          if (isAsyncWrapper(initialVNode)) {
+          if (isAsyncWrapperVNode) {
             ;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
               // note: we are moving the render call into an async callback,
               // which means it won't track dependencies - but it's ok because
@@ -1400,7 +1404,10 @@ function baseCreateRenderer(
           queuePostRenderEffect(m, parentSuspense)
         }
         // onVnodeMounted
-        if ((vnodeHook = props && props.onVnodeMounted)) {
+        if (
+          !isAsyncWrapperVNode &&
+          (vnodeHook = props && props.onVnodeMounted)
+        ) {
           const scopedInitialVNode = initialVNode
           queuePostRenderEffect(
             () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
@@ -2085,9 +2092,13 @@ function baseCreateRenderer(
     }
 
     const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
+    const shouldInvokeVnodeHook = !isAsyncWrapper(vnode)
 
     let vnodeHook: VNodeHook | undefined | null
-    if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
+    if (
+      shouldInvokeVnodeHook &&
+      (vnodeHook = props && props.onVnodeBeforeUnmount)
+    ) {
       invokeVNodeHook(vnodeHook, parentComponent, vnode)
     }
 
@@ -2140,7 +2151,11 @@ function baseCreateRenderer(
       }
     }
 
-    if ((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {
+    if (
+      (shouldInvokeVnodeHook &&
+        (vnodeHook = props && props.onVnodeUnmounted)) ||
+      shouldInvokeDirs
+    ) {
       queuePostRenderEffect(() => {
         vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
         shouldInvokeDirs &&