Просмотр исходного кода

fix(runtime-vapor): cache interop slot wrappers by source slot

daiwei 3 недель назад
Родитель
Сommit
80c551c027

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

@@ -753,6 +753,37 @@ describe('vdomInterop', () => {
       expect(html()).toBe('<div>direct call slot</div>')
     })
 
+    test('slots.default() access should return a stable wrapper', () => {
+      const VDomChild = defineComponent({
+        setup(_, { slots }) {
+          const first = slots.default
+          const second = slots.default
+          return () => h('div', String(first === second))
+        },
+      })
+
+      const VaporChild = defineVaporComponent({
+        setup() {
+          return createComponent(
+            VDomChild as any,
+            null,
+            {
+              default: () => template('stable slot wrapper')(),
+            },
+            true,
+          )
+        },
+      })
+
+      const { html } = define({
+        setup() {
+          return () => h(VaporChild as any)
+        },
+      }).render()
+
+      expect(html()).toBe('<div>true</div>')
+    })
+
     test('slots.default() with slot props', () => {
       const VDomChild = defineComponent({
         setup(_, { slots }) {

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

@@ -506,17 +506,32 @@ const vaporSlotPropsProxyHandler: ProxyHandler<
   },
 }
 
+// Cache wrappers per raw slots object so repeated `slots.default` access keeps
+// a stable function identity
+const vaporSlotWrappersCache = new WeakMap<
+  object,
+  Map<PropertyKey, { slot: Function; wrapped: Function }>
+>()
+
 const vaporSlotsProxyHandler: ProxyHandler<any> = {
   get(target, key) {
     const slot = target[key]
     if (isFunction(slot)) {
       slot.__vapor = true
+      let wrappers = vaporSlotWrappersCache.get(target)
+      if (!wrappers) vaporSlotWrappersCache.set(target, (wrappers = new Map()))
+      const cached = wrappers.get(key)
+      if (cached && cached.slot === slot) {
+        return cached.wrapped
+      }
+
       // Create a wrapper that internally uses renderSlot for proper vapor slot handling
       // This ensures that calling slots.default() works the same as renderSlot(slots, 'default')
       const wrapped = (props?: Record<string, any>) => [
         renderSlot({ [key]: slot }, key as string, props),
       ]
       ;(wrapped as any).__vs = slot
+      wrappers.set(key, { slot, wrapped })
       return wrapped
     }
     return slot