Преглед на файлове

fix(custom-element): ensure proper remount and prevent redundant slot parsing with shadowRoot false (#13201)

close #13199
edison преди 11 месеца
родител
ревизия
1d41d4de7f
променени са 2 файла, в които са добавени 89 реда и са изтрити 3 реда
  1. 86 0
      packages/runtime-dom/__tests__/customElement.spec.ts
  2. 3 3
      packages/runtime-dom/src/apiCustomElement.ts

+ 86 - 0
packages/runtime-dom/__tests__/customElement.spec.ts

@@ -1226,6 +1226,92 @@ describe('defineCustomElement', () => {
       expect(target.innerHTML).toBe(`<span>default</span>`)
       app.unmount()
     })
+
+    test('toggle nested custom element with shadowRoot: false', async () => {
+      customElements.define(
+        'my-el-child-shadow-false',
+        defineCustomElement(
+          {
+            render(ctx: any) {
+              return h('div', null, [renderSlot(ctx.$slots, 'default')])
+            },
+          },
+          { shadowRoot: false },
+        ),
+      )
+      const ChildWrapper = {
+        render() {
+          return h('my-el-child-shadow-false', null, 'child')
+        },
+      }
+
+      customElements.define(
+        'my-el-parent-shadow-false',
+        defineCustomElement(
+          {
+            props: {
+              isShown: { type: Boolean, required: true },
+            },
+            render(ctx: any, _: any, $props: any) {
+              return $props.isShown
+                ? h('div', { key: 0 }, [renderSlot(ctx.$slots, 'default')])
+                : null
+            },
+          },
+          { shadowRoot: false },
+        ),
+      )
+      const ParentWrapper = {
+        props: {
+          isShown: { type: Boolean, required: true },
+        },
+        render(ctx: any, _: any, $props: any) {
+          return h('my-el-parent-shadow-false', { isShown: $props.isShown }, [
+            renderSlot(ctx.$slots, 'default'),
+          ])
+        },
+      }
+
+      const isShown = ref(true)
+      const App = {
+        render() {
+          return h(ParentWrapper, { isShown: isShown.value } as any, {
+            default: () => [h(ChildWrapper)],
+          })
+        },
+      }
+      const container = document.createElement('div')
+      document.body.appendChild(container)
+      const app = createApp(App)
+      app.mount(container)
+      expect(container.innerHTML).toBe(
+        `<my-el-parent-shadow-false is-shown="" data-v-app="">` +
+          `<div>` +
+          `<my-el-child-shadow-false data-v-app="">` +
+          `<div>child</div>` +
+          `</my-el-child-shadow-false>` +
+          `</div>` +
+          `</my-el-parent-shadow-false>`,
+      )
+
+      isShown.value = false
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<my-el-parent-shadow-false data-v-app=""><!----></my-el-parent-shadow-false>`,
+      )
+
+      isShown.value = true
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<my-el-parent-shadow-false data-v-app="" is-shown="">` +
+          `<div>` +
+          `<my-el-child-shadow-false data-v-app="">` +
+          `<div>child</div>` +
+          `</my-el-child-shadow-false>` +
+          `</div>` +
+          `</my-el-parent-shadow-false>`,
+      )
+    })
   })
 
   describe('helpers', () => {

+ 3 - 3
packages/runtime-dom/src/apiCustomElement.ts

@@ -280,7 +280,8 @@ export class VueElement
     // avoid resolving component if it's not connected
     if (!this.isConnected) return
 
-    if (!this.shadowRoot) {
+    // avoid re-parsing slots if already resolved
+    if (!this.shadowRoot && !this._resolved) {
       this._parseSlots()
     }
     this._connected = true
@@ -298,8 +299,7 @@ export class VueElement
 
     if (!this._instance) {
       if (this._resolved) {
-        this._setParent()
-        this._update()
+        this._mount(this._def)
       } else {
         if (parent && parent._pendingResolve) {
           this._pendingResolve = parent._pendingResolve.then(() => {