Skip to content

Commit 66cc461

Browse files
Doctor-wusxzz
authored andcommitted
perf(runtime-vapor): perf dynamic slots runtime
1 parent cc511e6 commit 66cc461

File tree

3 files changed

+115
-54
lines changed

3 files changed

+115
-54
lines changed

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

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ describe('component: slots', () => {
9797

9898
const { render } = define({
9999
render() {
100-
return createComponent(Child, {}, { _: 2 as any }, () => [
101-
flag1.value
102-
? { name: 'one', fn: () => template('<span></span>')() }
103-
: { name: 'two', fn: () => template('<div></div>')() },
100+
return createComponent(Child, {}, { _: 2 as any }, [
101+
() =>
102+
flag1.value
103+
? { name: 'one', fn: () => template('<span></span>')() }
104+
: { name: 'two', fn: () => template('<div></div>')() },
104105
])
105106
},
106107
})
@@ -132,10 +133,11 @@ describe('component: slots', () => {
132133

133134
const { render } = define({
134135
setup() {
135-
return createComponent(Child, {}, {}, () => [
136-
flag1.value
137-
? [{ name: 'header', fn: () => template('header')() }]
138-
: [{ name: 'footer', fn: () => template('footer')() }],
136+
return createComponent(Child, {}, {}, [
137+
() =>
138+
flag1.value
139+
? [{ name: 'header', fn: () => template('header')() }]
140+
: [{ name: 'footer', fn: () => template('footer')() }],
139141
])
140142
},
141143
})
@@ -178,8 +180,8 @@ describe('component: slots', () => {
178180
return template('content')()
179181
},
180182
},
181-
() => [
182-
[
183+
[
184+
() => [
183185
{
184186
name: 'inVFor',
185187
fn: () => {
@@ -188,14 +190,14 @@ describe('component: slots', () => {
188190
},
189191
},
190192
],
191-
{
193+
() => ({
192194
name: 'inVIf',
193195
key: '1',
194196
fn: () => {
195197
instanceInVIfSlot = getCurrentInstance()
196198
return template('content')()
197199
},
198-
},
200+
}),
199201
],
200202
)
201203
},
@@ -206,6 +208,61 @@ describe('component: slots', () => {
206208
expect(instanceInVIfSlot).toBe(instance)
207209
})
208210

211+
test('dynamicSlots should update separately', async () => {
212+
const flag1 = ref(true)
213+
const flag2 = ref(true)
214+
const slotFn1 = vitest.fn()
215+
const slotFn2 = vitest.fn()
216+
217+
let instance: any
218+
const Child = () => {
219+
instance = getCurrentInstance()
220+
return template('child')()
221+
}
222+
223+
const { render } = define({
224+
render() {
225+
return createComponent(Child, {}, {}, [
226+
() => {
227+
slotFn1()
228+
return flag1.value
229+
? { name: 'one', fn: () => template('one')() }
230+
: { name: 'two', fn: () => template('two')() }
231+
},
232+
() => {
233+
slotFn2()
234+
return flag2.value
235+
? { name: 'three', fn: () => template('three')() }
236+
: { name: 'four', fn: () => template('four')() }
237+
},
238+
])
239+
},
240+
})
241+
242+
render()
243+
244+
expect(instance.slots).toHaveProperty('one')
245+
expect(instance.slots).toHaveProperty('three')
246+
expect(slotFn1).toHaveBeenCalledTimes(1)
247+
expect(slotFn2).toHaveBeenCalledTimes(1)
248+
249+
flag1.value = false
250+
await nextTick()
251+
252+
expect(instance.slots).toHaveProperty('two')
253+
expect(instance.slots).toHaveProperty('three')
254+
expect(slotFn1).toHaveBeenCalledTimes(2)
255+
expect(slotFn2).toHaveBeenCalledTimes(1)
256+
257+
flag2.value = false
258+
await nextTick()
259+
260+
expect(instance.slots).toHaveProperty('two')
261+
expect(instance.slots).toHaveProperty('four')
262+
expect(slotFn1).toHaveBeenCalledTimes(2)
263+
expect(slotFn2).toHaveBeenCalledTimes(2)
264+
})
265+
209266
test.todo('should respect $stable flag', async () => {
210267
// TODO: $stable flag?
211268
})
@@ -299,8 +356,11 @@ describe('component: slots', () => {
299356

300357
const { host } = define(() => {
301358
// dynamic slot
302-
return createComponent(Comp, {}, {}, () => [
303-
{ name: 'header', fn: ({ title }) => template(`${title()}`)() },
359+
return createComponent(Comp, {}, {}, [
360+
() => ({
361+
name: 'header',
362+
fn: ({ title }) => template(`${title()}`)(),
363+
}),
304364
])
305365
}).render()
306366

@@ -363,10 +423,11 @@ describe('component: slots', () => {
363423
})
364424

365425
const { host } = define(() => {
366-
return createComponent(Child, {}, {}, () => [
367-
flag1.value
368-
? { name: 'one', fn: () => template('one content')() }
369-
: { name: 'two', fn: () => template('two content')() },
426+
return createComponent(Child, {}, {}, [
427+
() =>
428+
flag1.value
429+
? { name: 'one', fn: () => template('one content')() }
430+
: { name: 'two', fn: () => template('two content')() },
370431
])
371432
}).render()
372433

packages/runtime-vapor/src/componentSlots.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,50 +53,50 @@ export function initSlots(
5353

5454
if (dynamicSlots) {
5555
slots = shallowReactive(slots)
56-
const dynamicSlotKeys: Record<string, true> = {}
57-
firstEffect(instance, () => {
58-
const _dynamicSlots: (DynamicSlot | DynamicSlot[])[] =
59-
callWithAsyncErrorHandling(
60-
dynamicSlots,
61-
instance,
62-
VaporErrorCodes.RENDER_FUNCTION,
63-
)
64-
for (let i = 0; i < _dynamicSlots.length; i++) {
65-
const slot = _dynamicSlots[i]
56+
const dynamicSlotRecords: Record<string, boolean>[] = []
57+
dynamicSlots.forEach((fn, index) => {
58+
firstEffect(instance, () => {
59+
const slotRecord = (dynamicSlotRecords[index] =
60+
dynamicSlotRecords[index] || {})
61+
const dynamicSlot: DynamicSlot | DynamicSlot[] =
62+
callWithAsyncErrorHandling(
63+
fn,
64+
instance,
65+
VaporErrorCodes.RENDER_FUNCTION,
66+
)
6667
// array of dynamic slot generated by <template v-for="..." #[...]>
67-
if (isArray(slot)) {
68-
for (let j = 0; j < slot.length; j++) {
69-
slots[slot[j].name] = withCtx(slot[j].fn)
70-
dynamicSlotKeys[slot[j].name] = true
68+
if (isArray(dynamicSlot)) {
69+
for (let j = 0; j < dynamicSlot.length; j++) {
70+
slots[dynamicSlot[j].name] = withCtx(dynamicSlot[j].fn)
71+
slotRecord[dynamicSlot[j].name] = true
7172
}
72-
} else if (slot) {
73+
} else if (dynamicSlot) {
7374
// conditional single slot generated by <template v-if="..." #foo>
74-
slots[slot.name] = withCtx(
75-
slot.key
75+
slots[dynamicSlot.name] = withCtx(
76+
dynamicSlot.key
7677
? (...args: any[]) => {
77-
const res = slot.fn(...args)
78+
const res = dynamicSlot.fn(...args)
7879
// attach branch key so each conditional branch is considered a
7980
// different fragment
80-
if (res) (res as any).key = slot.key
81+
if (res) (res as any).key = dynamicSlot.key
8182
return res
8283
}
83-
: slot.fn,
84+
: dynamicSlot.fn,
8485
)
85-
dynamicSlotKeys[slot.name] = true
86+
slotRecord[dynamicSlot.name] = true
8687
}
87-
}
88-
// delete stale slots
89-
for (const key in dynamicSlotKeys) {
90-
if (
91-
!_dynamicSlots.some(slot =>
92-
slot && isArray(slot)
93-
? slot.some(s => s.name === key)
94-
: slot.name === key,
95-
)
96-
) {
97-
delete slots[key]
88+
// delete stale slots
89+
for (const key in slotRecord) {
90+
if (
91+
!(dynamicSlot && isArray(dynamicSlot)
92+
? dynamicSlot.some(s => s.name === key)
93+
: dynamicSlot.name === key)
94+
) {
95+
slotRecord[key] = false
96+
delete slots[key]
97+
}
9898
}
99-
}
99+
})
100100
})
101101
}
102102

packages/runtime-vapor/src/errorHandling.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ export function callWithErrorHandling(
8888
return res
8989
}
9090

91-
export function callWithAsyncErrorHandling(
92-
fn: Function | Function[],
91+
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
92+
fn: F,
9393
instance: ComponentInternalInstance | null,
9494
type: ErrorTypes,
9595
args?: unknown[],
96-
): any[] {
96+
): F extends Function ? any : any[] {
9797
if (isFunction(fn)) {
9898
const res = callWithErrorHandling(fn, instance, type, args)
9999
if (res && isPromise(res)) {

0 commit comments

Comments
 (0)