Explorar o código

fix(vdomInterop): keep interop fragment nodes in sync after vnode updates

daiwei hai 1 mes
pai
achega
c62dc1f284

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

@@ -4,6 +4,7 @@ import {
   Suspense,
   Teleport,
   createApp,
+  createCommentVNode,
   createVNode,
   defineComponent,
   h,
@@ -38,6 +39,7 @@ import {
   createTemplateRefSetter,
   defineVaporAsyncComponent,
   defineVaporComponent,
+  insert,
   renderEffect,
   setText,
   template,
@@ -73,6 +75,35 @@ describe('vdomInterop', () => {
     })
   })
 
+  describe('fragment nodes', () => {
+    test('refreshes interop fragment nodes after component root updates', async () => {
+      const show = ref(false)
+      const VDomChild = defineComponent({
+        setup() {
+          return () =>
+            show.value ? h('div', 'child') : createCommentVNode('v-if', true)
+        },
+      })
+
+      const app = createApp({ render: () => null })
+      app.use(vaporInteropPlugin)
+      const vapor = (app._context as any).vapor
+      const host = document.createElement('div')
+
+      const frag = vapor.vdomMount(VDomChild, null)
+      insert(frag, host)
+
+      expect(host.innerHTML).toBe('<!--v-if-->')
+      expect(frag.nodes).toBeInstanceOf(Comment)
+
+      show.value = true
+      await nextTick()
+
+      expect(host.innerHTML).toBe('<div>child</div>')
+      expect(frag.nodes).toBeInstanceOf(HTMLDivElement)
+    })
+  })
+
   describe('props', () => {
     test('should work if props are not provided', () => {
       const VaporChild = defineVaporComponent({

+ 17 - 0
packages/runtime-vapor/src/vdomInterop.ts

@@ -559,6 +559,21 @@ function resolveVNodeNodes(vnode: VNode): Block {
   return vnode.el as Block
 }
 
+function trackFragmentVNodeUpdates(frag: VaporFragment, vnode: VNode): void {
+  const refresh = () => {
+    frag.nodes = resolveVNodeNodes(vnode)
+    if (frag.onUpdated) frag.onUpdated.forEach(m => m())
+  }
+
+  const props = (vnode.props ||= {})
+  const existing = props.onVnodeUpdated
+  props.onVnodeUpdated = existing
+    ? isArray(existing)
+      ? [...existing, refresh]
+      : [existing, refresh]
+    : refresh
+}
+
 /**
  * Mount VNode in vapor
  */
@@ -572,6 +587,7 @@ function mountVNode(
   const frag = new VaporFragment<Block>([])
   frag.vnode = vnode
   frag.$key = vnode.key
+  trackFragmentVNodeUpdates(frag, vnode)
 
   let isMounted = false
   const unmount = (parentNode?: ParentNode, transition?: TransitionHooks) => {
@@ -679,6 +695,7 @@ function createVDOMComponent(
     rawProps && extend({}, new Proxy(rawProps, rawPropsProxyHandlers)),
   ))
   frag.$key = vnode.key
+  trackFragmentVNodeUpdates(frag, vnode)
 
   if (currentKeepAliveCtx) {
     currentKeepAliveCtx.processShapeFlag(frag)