Skip to content

Commit ec13986

Browse files
Doctor-wusxzz
authored andcommitted
fix(runtime-vapor): fix loop slots cleanup and add some annotate
1 parent 8a55a0d commit ec13986

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

packages/runtime-vapor/__tests__/componentSlots.spec.ts

+70
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,76 @@ describe('component: slots', () => {
392392
)
393393
})
394394

395+
test('should cleanup all slots when loop slot has same key', async () => {
396+
const loop = ref([1, 1, 1])
397+
398+
let childInstance
399+
const t0 = template('<div></div>')
400+
const { component: Child } = define({
401+
setup() {
402+
childInstance = getCurrentInstance()
403+
const slots = useSlots()
404+
const keys = () => Object.keys(slots)
405+
return {
406+
keys,
407+
slots,
408+
}
409+
},
410+
render: (_ctx: any) => {
411+
const n0 = createFor(
412+
() => _ctx.keys(),
413+
(_ctx0: any) => {
414+
const n5 = t0()
415+
const n4 = createSlot(() => _ctx0[0])
416+
insert(n4, n5 as ParentNode)
417+
return n5
418+
},
419+
)
420+
return n0
421+
},
422+
})
423+
424+
const t1 = template(' static default ')
425+
const { render } = define({
426+
setup() {
427+
return createComponent(Child, {}, [
428+
{
429+
default: () => {
430+
return t1()
431+
},
432+
},
433+
() =>
434+
createForSlots(loop.value, (item, i) => ({
435+
name: item,
436+
fn: () => template(item)(),
437+
})),
438+
])
439+
},
440+
})
441+
const { html } = render()
442+
expect(childInstance!.slots).toHaveProperty('1')
443+
expect(childInstance!.slots).toHaveProperty('default')
444+
expect(html()).toBe(
445+
'<div>1<!--slot--></div><div> static default <!--slot--></div><!--for-->',
446+
)
447+
loop.value = [1]
448+
await nextTick()
449+
expect(childInstance!.slots).toHaveProperty('1')
450+
expect(childInstance!.slots).toHaveProperty('default')
451+
expect(html()).toBe(
452+
'<div>1<!--slot--></div><div> static default <!--slot--></div><!--for-->',
453+
)
454+
loop.value = [1, 2, 3]
455+
await nextTick()
456+
expect(childInstance!.slots).toHaveProperty('1')
457+
expect(childInstance!.slots).toHaveProperty('2')
458+
expect(childInstance!.slots).toHaveProperty('3')
459+
expect(childInstance!.slots).toHaveProperty('default')
460+
expect(html()).toBe(
461+
'<div>1<!--slot--></div><div>2<!--slot--></div><div>3<!--slot--></div><div> static default <!--slot--></div><!--for-->',
462+
)
463+
})
464+
395465
test('dynamicSlots should not cover high level slots', async () => {
396466
const dynamicFlag = ref(true)
397467

packages/runtime-vapor/src/componentSlots.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ export function initSlots(
5252

5353
instance.slots = shallowReactive({})
5454
const renderedSlotKeys: Set<string>[] = []
55+
/**
56+
* Maintain a queue for each slot name, so that we can
57+
* render the next slot when the highest level slot was removed
58+
*/
5559
const slotsQueue: Record<string, [level: number, slot: Slot][]> = {}
5660
rawSlots.forEach((slots, index) => {
5761
const isDynamicSlot = isDynamicSlotFn(slots)
5862
if (isDynamicSlot) {
5963
firstEffect(instance, () => {
6064
const renderedKeys = (renderedSlotKeys[index] ||= new Set())
6165
let dynamicSlot = slots()
66+
// cleanup slots and re-calc to avoid diffing slots between renders
67+
// cleanup will return a slotNames array contains the slot names that need to be restored
6268
const restoreSlotNames = cleanupSlot(index)
6369
if (isArray(dynamicSlot)) {
6470
for (const slot of dynamicSlot) {
@@ -67,6 +73,7 @@ export function initSlots(
6773
} else if (dynamicSlot) {
6874
registerSlot(dynamicSlot.name, dynamicSlot.fn, index, renderedKeys)
6975
}
76+
// restore after re-calc slots
7077
if (restoreSlotNames.length) {
7178
for (const key of restoreSlotNames) {
7279
const [restoreLevel, restoreFn] = slotsQueue[key][0]
@@ -75,6 +82,7 @@ export function initSlots(
7582
addSlot(key, restoreFn)
7683
}
7784
}
85+
// delete stale slots
7886
for (const name of renderedKeys) {
7987
if (
8088
!(isArray(dynamicSlot)
@@ -95,14 +103,16 @@ export function initSlots(
95103

96104
function cleanupSlot(level: number) {
97105
const restoreSlotNames: string[] = []
106+
// remove slots from all queues
98107
Object.keys(slotsQueue).forEach(slotName => {
99108
const index = slotsQueue[slotName].findIndex(([l]) => l === level)
100109
if (index > -1) {
101-
slotsQueue[slotName].splice(index, 1)
110+
slotsQueue[slotName] = slotsQueue[slotName].filter(([l]) => l !== level)
102111
if (!slotsQueue[slotName].length) {
103112
delete slotsQueue[slotName]
104113
return
105114
}
115+
// restore next slot if the removed slots was the highest level slot
106116
if (index === 0) {
107117
renderedSlotKeys[level] && renderedSlotKeys[level].delete(slotName)
108118
restoreSlotNames.push(slotName)
@@ -121,13 +131,15 @@ export function initSlots(
121131
slotsQueue[name] ||= []
122132
slotsQueue[name].push([level, slot])
123133
slotsQueue[name].sort((a, b) => b[0] - a[0])
134+
// hide old slot if the registered slot is the highest level
124135
if (slotsQueue[name][1]) {
125136
const hidenLevel = slotsQueue[name][1][0]
126137
renderedSlotKeys[hidenLevel] && renderedSlotKeys[hidenLevel].delete(name)
127138
}
128139
if (slotsQueue[name][0][0] === level) {
129140
renderedKeys && renderedKeys.add(name)
130141
}
142+
// render the highest level slot
131143
addSlot(name, slotsQueue[name][0][1])
132144
}
133145

0 commit comments

Comments
 (0)