Переглянути джерело

perf(compiler-vapor/runtime-vapor): finer update granularity (#222)

Doctor Wu 1 рік тому
батько
коміт
8a59311a22

+ 10 - 10
packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

@@ -6,13 +6,13 @@ const t0 = _template("foo")
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n2 = _createComponent(_component_Comp, null, null, () => [{
+  const n2 = _createComponent(_component_Comp, null, null, [() => ({
     name: _ctx.name, 
     fn: () => {
       const n0 = t0()
       return n0
     }
-  }], true)
+  })], true)
   return n2
 }"
 `;
@@ -23,13 +23,13 @@ const t0 = _template("foo")
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
+  const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (item) => ({
     name: item, 
     fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
       const n0 = t0()
       return n0
     })
-  }))], true)
+  })))], true)
   return n2
 }"
 `;
@@ -40,13 +40,13 @@ const t0 = _template("foo")
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (_, __, index) => ({
+  const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (_, __, index) => ({
     name: index, 
     fn: () => {
       const n0 = t0()
       return n0
     }
-  }))], true)
+  })))], true)
   return n2
 }"
 `;
@@ -59,7 +59,7 @@ const t2 = _template("else condition")
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n6 = _createComponent(_component_Comp, null, null, () => [_ctx.condition
+  const n6 = _createComponent(_component_Comp, null, null, [() => (_ctx.condition
     ? {
       name: "condition", 
       fn: () => {
@@ -84,7 +84,7 @@ export function render(_ctx) {
           return n4
         }, 
         key: "2"
-      }], true)
+      })], true)
   return n6
 }"
 `;
@@ -151,13 +151,13 @@ exports[`compiler: transform slot > on component dynamically named slot 1`] = `
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
-  const n1 = _createComponent(_component_Comp, null, {  }, () => [{
+  const n1 = _createComponent(_component_Comp, null, {  }, [() => ({
     name: _ctx.named, 
     fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
       const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
       return n0
     })
-  }], true)
+  })], true)
   return n1
 }"
 `;

+ 12 - 6
packages/compiler-vapor/src/generators/component.ts

@@ -168,24 +168,30 @@ function genDynamicSlots(
   dynamicSlots: ComponentDynamicSlot[],
   context: CodegenContext,
 ) {
-  const slotsExpr = genMulti(
+  return genMulti(
     dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
-    ...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
+    ...dynamicSlots.map(slot => genDynamicSlot(slot, context, true)),
   )
-  return ['() => ', ...slotsExpr]
 }
 
 function genDynamicSlot(
   slot: ComponentDynamicSlot,
   context: CodegenContext,
+  top = false,
 ): CodeFragment[] {
   switch (slot.slotType) {
     case DynamicSlotType.BASIC:
-      return genBasicDynamicSlot(slot, context)
+      return top
+        ? ['() => (', ...genBasicDynamicSlot(slot, context), ')']
+        : genBasicDynamicSlot(slot, context)
     case DynamicSlotType.LOOP:
-      return genLoopSlot(slot, context)
+      return top
+        ? ['() => (', ...genLoopSlot(slot, context), ')']
+        : genLoopSlot(slot, context)
     case DynamicSlotType.CONDITIONAL:
-      return genConditionalSlot(slot, context)
+      return top
+        ? ['() => (', ...genConditionalSlot(slot, context), ')']
+        : genConditionalSlot(slot, context)
   }
 }
 

+ 79 - 18
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -97,10 +97,11 @@ describe('component: slots', () => {
 
     const { render } = define({
       render() {
-        return createComponent(Child, {}, { _: 2 as any }, () => [
-          flag1.value
-            ? { name: 'one', fn: () => template('<span></span>')() }
-            : { name: 'two', fn: () => template('<div></div>')() },
+        return createComponent(Child, {}, { _: 2 as any }, [
+          () =>
+            flag1.value
+              ? { name: 'one', fn: () => template('<span></span>')() }
+              : { name: 'two', fn: () => template('<div></div>')() },
         ])
       },
     })
@@ -132,10 +133,11 @@ describe('component: slots', () => {
 
     const { render } = define({
       setup() {
-        return createComponent(Child, {}, {}, () => [
-          flag1.value
-            ? [{ name: 'header', fn: () => template('header')() }]
-            : [{ name: 'footer', fn: () => template('footer')() }],
+        return createComponent(Child, {}, {}, [
+          () =>
+            flag1.value
+              ? [{ name: 'header', fn: () => template('header')() }]
+              : [{ name: 'footer', fn: () => template('footer')() }],
         ])
       },
     })
@@ -178,8 +180,8 @@ describe('component: slots', () => {
               return template('content')()
             },
           },
-          () => [
-            [
+          [
+            () => [
               {
                 name: 'inVFor',
                 fn: () => {
@@ -188,14 +190,14 @@ describe('component: slots', () => {
                 },
               },
             ],
-            {
+            () => ({
               name: 'inVIf',
               key: '1',
               fn: () => {
                 instanceInVIfSlot = getCurrentInstance()
                 return template('content')()
               },
-            },
+            }),
           ],
         )
       },
@@ -206,6 +208,61 @@ describe('component: slots', () => {
     expect(instanceInVIfSlot).toBe(instance)
   })
 
+  test('dynamicSlots should update separately', async () => {
+    const flag1 = ref(true)
+    const flag2 = ref(true)
+    const slotFn1 = vitest.fn()
+    const slotFn2 = vitest.fn()
+
+    let instance: any
+    const Child = () => {
+      instance = getCurrentInstance()
+      return template('child')()
+    }
+
+    const { render } = define({
+      render() {
+        return createComponent(Child, {}, {}, [
+          () => {
+            slotFn1()
+            return flag1.value
+              ? { name: 'one', fn: () => template('one')() }
+              : { name: 'two', fn: () => template('two')() }
+          },
+          () => {
+            slotFn2()
+            return flag2.value
+              ? { name: 'three', fn: () => template('three')() }
+              : { name: 'four', fn: () => template('four')() }
+          },
+        ])
+      },
+    })
+
+    render()
+
+    expect(instance.slots).toHaveProperty('one')
+    expect(instance.slots).toHaveProperty('three')
+    expect(slotFn1).toHaveBeenCalledTimes(1)
+    expect(slotFn2).toHaveBeenCalledTimes(1)
+
+    flag1.value = false
+    await nextTick()
+
+    expect(instance.slots).toHaveProperty('two')
+    expect(instance.slots).toHaveProperty('three')
+    expect(slotFn1).toHaveBeenCalledTimes(2)
+    expect(slotFn2).toHaveBeenCalledTimes(1)
+
+    flag2.value = false
+    await nextTick()
+
+    expect(instance.slots).toHaveProperty('two')
+    expect(instance.slots).toHaveProperty('four')
+    expect(slotFn1).toHaveBeenCalledTimes(2)
+    expect(slotFn2).toHaveBeenCalledTimes(2)
+  })
+
   test.todo('should respect $stable flag', async () => {
     // TODO: $stable flag?
   })
@@ -299,8 +356,11 @@ describe('component: slots', () => {
 
       const { host } = define(() => {
         // dynamic slot
-        return createComponent(Comp, {}, {}, () => [
-          { name: 'header', fn: ({ title }) => template(`${title()}`)() },
+        return createComponent(Comp, {}, {}, [
+          () => ({
+            name: 'header',
+            fn: ({ title }) => template(`${title()}`)(),
+          }),
         ])
       }).render()
 
@@ -363,10 +423,11 @@ describe('component: slots', () => {
       })
 
       const { host } = define(() => {
-        return createComponent(Child, {}, {}, () => [
-          flag1.value
-            ? { name: 'one', fn: () => template('one content')() }
-            : { name: 'two', fn: () => template('two content')() },
+        return createComponent(Child, {}, {}, [
+          () =>
+            flag1.value
+              ? { name: 'one', fn: () => template('one content')() }
+              : { name: 'two', fn: () => template('two content')() },
         ])
       }).render()
 

+ 37 - 34
packages/runtime-vapor/src/componentSlots.ts

@@ -33,7 +33,9 @@ export interface DynamicSlot {
   key?: string
 }
 
-export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
+type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
+
+export type DynamicSlots = DynamicSlotFn[]
 
 export function initSlots(
   instance: ComponentInternalInstance,
@@ -51,50 +53,51 @@ export function initSlots(
 
   if (dynamicSlots) {
     slots = shallowReactive(slots)
-    const dynamicSlotKeys: Record<string, true> = {}
-    firstEffect(instance, () => {
-      const _dynamicSlots: (DynamicSlot | DynamicSlot[])[] =
-        callWithAsyncErrorHandling(
-          dynamicSlots,
-          instance,
-          VaporErrorCodes.RENDER_FUNCTION,
-        )
-      for (let i = 0; i < _dynamicSlots.length; i++) {
-        const slot = _dynamicSlots[i]
+    const dynamicSlotRecords: Record<string, boolean>[] = []
+    dynamicSlots.forEach((fn, index) => {
+      firstEffect(instance, () => {
+        const slotRecord = (dynamicSlotRecords[index] =
+          dynamicSlotRecords[index] || {})
+        const dynamicSlot: DynamicSlot | DynamicSlot[] =
+          callWithAsyncErrorHandling(
+            fn,
+            instance,
+            VaporErrorCodes.RENDER_FUNCTION,
+          )
         // array of dynamic slot generated by <template v-for="..." #[...]>
-        if (isArray(slot)) {
-          for (let j = 0; j < slot.length; j++) {
-            slots[slot[j].name] = withCtx(slot[j].fn)
-            dynamicSlotKeys[slot[j].name] = true
+        if (isArray(dynamicSlot)) {
+          for (let j = 0; j < dynamicSlot.length; j++) {
+            slots[dynamicSlot[j].name] = withCtx(dynamicSlot[j].fn)
+            slotRecord[dynamicSlot[j].name] = true
           }
-        } else if (slot) {
+        } else if (dynamicSlot) {
           // conditional single slot generated by <template v-if="..." #foo>
-          slots[slot.name] = withCtx(
-            slot.key
+          slots[dynamicSlot.name] = withCtx(
+            dynamicSlot.key
               ? (...args: any[]) => {
-                  const res = slot.fn(...args)
+                  const res = dynamicSlot.fn(...args)
                   // attach branch key so each conditional branch is considered a
                   // different fragment
-                  if (res) (res as any).key = slot.key
+                  if (res) (res as any).key = dynamicSlot.key
                   return res
                 }
-              : slot.fn,
+              : dynamicSlot.fn,
           )
-          dynamicSlotKeys[slot.name] = true
+          slotRecord[dynamicSlot.name] = true
         }
-      }
-      // delete stale slots
-      for (const key in dynamicSlotKeys) {
-        if (
-          !_dynamicSlots.some(slot =>
-            slot && isArray(slot)
-              ? slot.some(s => s.name === key)
-              : slot.name === key,
-          )
-        ) {
-          delete slots[key]
+        // delete stale slots
+        for (const key in slotRecord) {
+          if (
+            slotRecord[key] &&
+            !(dynamicSlot && isArray(dynamicSlot)
+              ? dynamicSlot.some(s => s.name === key)
+              : dynamicSlot.name === key)
+          ) {
+            slotRecord[key] = false
+            delete slots[key]
+          }
         }
-      }
+      })
     })
   }
 

+ 3 - 3
packages/runtime-vapor/src/errorHandling.ts

@@ -88,12 +88,12 @@ export function callWithErrorHandling(
   return res
 }
 
-export function callWithAsyncErrorHandling(
-  fn: Function | Function[],
+export function callWithAsyncErrorHandling<F extends Function | Function[]>(
+  fn: F,
   instance: ComponentInternalInstance | null,
   type: ErrorTypes,
   args?: unknown[],
-): any[] {
+): F extends Function ? any : any[] {
   if (isFunction(fn)) {
     const res = callWithErrorHandling(fn, instance, type, args)
     if (res && isPromise(res)) {