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])
}