diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 05b8f1cc2..65e02e0bf 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/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 }" `; diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index b491b48f8..041c6adb6 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/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) } } diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index d769c0f19..5f115d8cc 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/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('')() } - : { name: 'two', fn: () => template('
')() }, + return createComponent(Child, {}, { _: 2 as any }, [ + () => + flag1.value + ? { name: 'one', fn: () => template('')() } + : { name: 'two', fn: () => template('
')() }, ]) }, }) @@ -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() diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 6b0db6cd4..943f7bbf1 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/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 = {} - 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[] = [] + 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