@@ -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