diff --git a/packages/runtime-core/__tests__/apiAsyncComponent.spec.ts b/packages/runtime-core/__tests__/apiAsyncComponent.spec.ts index 6f16aa8d303..b771ba9652d 100644 --- a/packages/runtime-core/__tests__/apiAsyncComponent.spec.ts +++ b/packages/runtime-core/__tests__/apiAsyncComponent.spec.ts @@ -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) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index f1f2f95214d..fb7e6a46c85 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -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 &&