diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index b1262ada7..62e99da38 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -392,6 +392,76 @@ describe('component: slots', () => { ) }) + test('should cleanup all slots when loop slot has same key', async () => { + const loop = ref([1, 1, 1]) + + let childInstance + const t0 = template('
') + const { component: Child } = define({ + setup() { + childInstance = getCurrentInstance() + const slots = useSlots() + const keys = () => Object.keys(slots) + return { + keys, + slots, + } + }, + render: (_ctx: any) => { + const n0 = createFor( + () => _ctx.keys(), + (_ctx0: any) => { + const n5 = t0() + const n4 = createSlot(() => _ctx0[0]) + insert(n4, n5 as ParentNode) + return n5 + }, + ) + return n0 + }, + }) + + const t1 = template(' static default ') + const { render } = define({ + setup() { + return createComponent(Child, {}, [ + { + default: () => { + return t1() + }, + }, + () => + createForSlots(loop.value, (item, i) => ({ + name: item, + fn: () => template(item)(), + })), + ]) + }, + }) + const { html } = render() + expect(childInstance!.slots).toHaveProperty('1') + expect(childInstance!.slots).toHaveProperty('default') + expect(html()).toBe( + '
1
static default
', + ) + loop.value = [1] + await nextTick() + expect(childInstance!.slots).toHaveProperty('1') + expect(childInstance!.slots).toHaveProperty('default') + expect(html()).toBe( + '
1
static default
', + ) + loop.value = [1, 2, 3] + await nextTick() + expect(childInstance!.slots).toHaveProperty('1') + expect(childInstance!.slots).toHaveProperty('2') + expect(childInstance!.slots).toHaveProperty('3') + expect(childInstance!.slots).toHaveProperty('default') + expect(html()).toBe( + '
1
2
3
static default
', + ) + }) + test('dynamicSlots should not cover high level slots', async () => { const dynamicFlag = ref(true) diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index ea12017be..b817863fa 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -52,6 +52,10 @@ export function initSlots( instance.slots = shallowReactive({}) const renderedSlotKeys: Set[] = [] + /** + * Maintain a queue for each slot name, so that we can + * render the next slot when the highest level slot was removed + */ const slotsQueue: Record = {} rawSlots.forEach((slots, index) => { const isDynamicSlot = isDynamicSlotFn(slots) @@ -59,6 +63,8 @@ export function initSlots( firstEffect(instance, () => { const renderedKeys = (renderedSlotKeys[index] ||= new Set()) let dynamicSlot = slots() + // cleanup slots and re-calc to avoid diffing slots between renders + // cleanup will return a slotNames array contains the slot names that need to be restored const restoreSlotNames = cleanupSlot(index) if (isArray(dynamicSlot)) { for (const slot of dynamicSlot) { @@ -67,6 +73,7 @@ export function initSlots( } else if (dynamicSlot) { registerSlot(dynamicSlot.name, dynamicSlot.fn, index, renderedKeys) } + // restore after re-calc slots if (restoreSlotNames.length) { for (const key of restoreSlotNames) { const [restoreLevel, restoreFn] = slotsQueue[key][0] @@ -75,6 +82,7 @@ export function initSlots( addSlot(key, restoreFn) } } + // delete stale slots for (const name of renderedKeys) { if ( !(isArray(dynamicSlot) @@ -95,14 +103,16 @@ export function initSlots( function cleanupSlot(level: number) { const restoreSlotNames: string[] = [] + // remove slots from all queues Object.keys(slotsQueue).forEach(slotName => { const index = slotsQueue[slotName].findIndex(([l]) => l === level) if (index > -1) { - slotsQueue[slotName].splice(index, 1) + slotsQueue[slotName] = slotsQueue[slotName].filter(([l]) => l !== level) if (!slotsQueue[slotName].length) { delete slotsQueue[slotName] return } + // restore next slot if the removed slots was the highest level slot if (index === 0) { renderedSlotKeys[level] && renderedSlotKeys[level].delete(slotName) restoreSlotNames.push(slotName) @@ -121,6 +131,7 @@ export function initSlots( slotsQueue[name] ||= [] slotsQueue[name].push([level, slot]) slotsQueue[name].sort((a, b) => b[0] - a[0]) + // hide old slot if the registered slot is the highest level if (slotsQueue[name][1]) { const hidenLevel = slotsQueue[name][1][0] renderedSlotKeys[hidenLevel] && renderedSlotKeys[hidenLevel].delete(name) @@ -128,6 +139,7 @@ export function initSlots( if (slotsQueue[name][0][0] === level) { renderedKeys && renderedKeys.add(name) } + // render the highest level slot addSlot(name, slotsQueue[name][0][1]) }