Skip to content

Commit c924742

Browse files
authored
perf(useSlots): pre-compute array matcher type, update snapshots
1 parent 91ac722 commit c924742

File tree

1 file changed

+32
-11
lines changed

1 file changed

+32
-11
lines changed

packages/react/src/hooks/useSlots.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,26 @@ export function useSlots<Config extends SlotConfig>(
3838
children: React.ReactNode,
3939
config: Config,
4040
): [Partial<SlotElements<Config>>, React.ReactNode[]] {
41-
// Object mapping slot names to their elements
42-
const slots: Partial<SlotElements<Config>> = {} as Partial<SlotElements<Config>>
43-
4441
// Array of elements that are not slots
4542
const rest: React.ReactNode[] = []
4643

4744
const keys = Object.keys(config) as Array<keyof Config>
4845
const values = Object.values(config)
4946
const totalSlots = keys.length
47+
48+
// Object mapping slot names to their elements, initialized with undefined for each key
49+
const slots: Partial<SlotElements<Config>> = {} as Partial<SlotElements<Config>>
50+
for (let i = 0; i < totalSlots; i++) {
51+
slots[keys[i]] = undefined
52+
}
53+
54+
// Pre-compute which slots use the [Component, testFn] matcher pattern
55+
// to avoid Array.isArray() checks in the hot inner loop
56+
const isArrayMatcher: boolean[] = new Array(totalSlots)
57+
for (let i = 0; i < totalSlots; i++) {
58+
isArrayMatcher[i] = Array.isArray(values[i])
59+
}
60+
5061
let slotsFound = 0
5162

5263
// eslint-disable-next-line github/array-foreach
@@ -56,26 +67,36 @@ export function useSlots<Config extends SlotConfig>(
5667
return
5768
}
5869

59-
// Short-circuit: if all slots are filled, remaining children go to rest
70+
// Fast path: once all slots are filled, only check for duplicates
6071
if (slotsFound === totalSlots) {
72+
for (let i = 0; i < totalSlots; i++) {
73+
if (isArrayMatcher[i]) {
74+
const [component, testFn] = values[i] as ComponentAndPropsMatcher
75+
if ((child.type === component || isSlot(child, component as SlotMarker)) && testFn(child.props)) {
76+
warning(true, `Found duplicate "${String(keys[i])}" slot. Only the first will be rendered.`)
77+
return
78+
}
79+
} else {
80+
if (child.type === values[i] || isSlot(child, values[i] as SlotMarker)) {
81+
warning(true, `Found duplicate "${String(keys[i])}" slot. Only the first will be rendered.`)
82+
return
83+
}
84+
}
85+
}
6186
rest.push(child)
6287
return
6388
}
6489

6590
let matchedIndex = -1
6691
for (let i = 0; i < totalSlots; i++) {
67-
// Skip already-filled slots
68-
if (slots[keys[i]] !== undefined) continue
69-
70-
const value = values[i]
71-
if (Array.isArray(value)) {
72-
const [component, testFn] = value
92+
if (isArrayMatcher[i]) {
93+
const [component, testFn] = values[i] as ComponentAndPropsMatcher
7394
if ((child.type === component || isSlot(child, component as SlotMarker)) && testFn(child.props)) {
7495
matchedIndex = i
7596
break
7697
}
7798
} else {
78-
if (child.type === value || isSlot(child, value as SlotMarker)) {
99+
if (child.type === values[i] || isSlot(child, values[i] as SlotMarker)) {
79100
matchedIndex = i
80101
break
81102
}

0 commit comments

Comments
 (0)