Jelajahi Sumber

fix(runtime-vapor): align interop mount hook order with vdom

daiwei 3 minggu lalu
induk
melakukan
b578a24885

+ 1 - 0
packages/runtime-core/src/apiCreateApp.ts

@@ -190,6 +190,7 @@ export interface VaporInteropInterface {
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     onBeforeMount?: () => void,
+    onVnodeBeforeMount?: () => void,
   ): GenericComponentInstance // VaporComponentInstance
   update(
     n1: VNode,

+ 18 - 0
packages/runtime-core/src/renderer.ts

@@ -1204,6 +1204,8 @@ function baseCreateRenderer(
             parentComponent!,
           )
         } else {
+          const vnodeBeforeMountHook =
+            !isAsyncWrapper(n2) && n2.props && n2.props.onVnodeBeforeMount
           getVaporInterface(parentComponent, n2).mount(
             n2,
             container,
@@ -1216,6 +1218,11 @@ function baseCreateRenderer(
                 invokeDirectiveHook(n2, null, parentComponent, 'beforeMount')
               }
             },
+            () => {
+              if (vnodeBeforeMountHook) {
+                invokeVNodeHook(vnodeBeforeMountHook, parentComponent, n2)
+              }
+            },
           )
           if (n2.dirs) {
             queuePostRenderEffect(
@@ -1224,6 +1231,17 @@ function baseCreateRenderer(
               parentSuspense,
             )
           }
+          const vnodeMountedHook =
+            !isAsyncWrapper(n2) && n2.props && n2.props.onVnodeMounted
+          if (vnodeMountedHook) {
+            const scopedVNode = n2
+            queuePostRenderEffect(
+              () =>
+                invokeVNodeHook(vnodeMountedHook, parentComponent, scopedVNode),
+              undefined,
+              parentSuspense,
+            )
+          }
         }
       } else {
         const shouldUpdate = shouldUpdateComponent(n1, n2, optimized)

+ 42 - 0
packages/runtime-vapor/__tests__/vdomInterop.spec.ts

@@ -320,6 +320,48 @@ describe('vdomInterop', () => {
       expect(vnodeUnmounted).toHaveBeenCalledTimes(1)
     })
 
+    test('should invoke vnode and directive mount hooks in VDOM order', async () => {
+      const calls: string[] = []
+      const vCustom = {
+        created: vi.fn(() => calls.push('directive created')),
+        beforeMount: vi.fn(() => calls.push('directive beforeMount')),
+        mounted: vi.fn(() => calls.push('directive mounted')),
+      }
+
+      const VaporChild = defineVaporComponent({
+        setup() {
+          return template('<div>vapor</div>')()
+        },
+      })
+
+      const App = defineComponent({
+        setup() {
+          return () =>
+            withDirectives(
+              h(VaporChild as any, {
+                onVnodeBeforeMount: () => calls.push('vnode beforeMount'),
+                onVnodeMounted: () => calls.push('vnode mounted'),
+              }),
+              [[vCustom]],
+            )
+        },
+      })
+
+      const root = document.createElement('div')
+      const app = createApp(App)
+      app.use(vaporInteropPlugin)
+      app.mount(root)
+      await nextTick()
+
+      expect(calls).toEqual([
+        'vnode beforeMount',
+        'directive created',
+        'directive beforeMount',
+        'directive mounted',
+        'vnode mounted',
+      ])
+    })
+
     test('should invoke update hooks in VDOM order on normal updates', async () => {
       const msg = ref('foo')
       const calls: string[] = []

+ 3 - 24
packages/runtime-vapor/src/vdomInterop.ts

@@ -143,6 +143,7 @@ const vaporInteropImpl: Omit<
     parentComponent,
     parentSuspense,
     onBeforeMount,
+    onVnodeBeforeMount,
   ) {
     let selfAnchor = (vnode.anchor = createTextNode())
     if (isHydrating) {
@@ -202,6 +203,8 @@ const vaporInteropImpl: Omit<
     if (rootEl) {
       vnode.el = rootEl
     }
+    // align with VDOM: vnode beforeMount runs before directive created/beforeMount.
+    onVnodeBeforeMount && onVnodeBeforeMount()
     // invoke directive hooks only when we have a valid root element
     if (vnode.dirs) {
       if (rootEl) {
@@ -217,32 +220,8 @@ const vaporInteropImpl: Omit<
       }
     }
 
-    // invoke onVnodeBeforeMount hook
-    const vnodeBeforeMountHook = vnode.props && vnode.props.onVnodeBeforeMount
-    if (vnodeBeforeMountHook) {
-      callWithAsyncErrorHandling(
-        vnodeBeforeMountHook,
-        parentComponent,
-        ErrorCodes.VNODE_HOOK,
-        [vnode],
-      )
-    }
-
     mountComponent(instance, container, selfAnchor)
 
-    // invoke onVnodeMounted hook
-    queuePostFlushCb(() => {
-      const vnodeHook = vnode.props && vnode.props.onVnodeMounted
-      if (vnodeHook) {
-        callWithAsyncErrorHandling(
-          vnodeHook,
-          parentComponent,
-          ErrorCodes.VNODE_HOOK,
-          [vnode],
-        )
-      }
-    })
-
     simpleSetCurrentInstance(prev)
     return instance
   },