@@ -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,39 @@ 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, skip matching entirely in production.
71+ // In dev, check for duplicates to warn.
6072 if ( slotsFound === totalSlots ) {
73+ if ( __DEV__ ) {
74+ for ( let i = 0 ; i < totalSlots ; i ++ ) {
75+ if ( isArrayMatcher [ i ] ) {
76+ const [ component , testFn ] = values [ i ] as ComponentAndPropsMatcher
77+ if ( ( child . type === component || isSlot ( child , component as SlotMarker ) ) && testFn ( child . props ) ) {
78+ warning ( true , `Found duplicate "${ String ( keys [ i ] ) } " slot. Only the first will be rendered.` )
79+ return
80+ }
81+ } else {
82+ if ( child . type === values [ i ] || isSlot ( child , values [ i ] as SlotMarker ) ) {
83+ warning ( true , `Found duplicate "${ String ( keys [ i ] ) } " slot. Only the first will be rendered.` )
84+ return
85+ }
86+ }
87+ }
88+ }
6189 rest . push ( child )
6290 return
6391 }
6492
6593 let matchedIndex = - 1
6694 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
95+ if ( isArrayMatcher [ i ] ) {
96+ const [ component , testFn ] = values [ i ] as ComponentAndPropsMatcher
7397 if ( ( child . type === component || isSlot ( child , component as SlotMarker ) ) && testFn ( child . props ) ) {
7498 matchedIndex = i
7599 break
76100 }
77101 } else {
78- if ( child . type === value || isSlot ( child , value as SlotMarker ) ) {
102+ if ( child . type === values [ i ] || isSlot ( child , values [ i ] as SlotMarker ) ) {
79103 matchedIndex = i
80104 break
81105 }
@@ -92,7 +116,9 @@ export function useSlots<Config extends SlotConfig>(
92116
93117 // If slot is already filled, ignore duplicates
94118 if ( slots [ slotKey ] !== undefined ) {
95- warning ( true , `Found duplicate "${ String ( slotKey ) } " slot. Only the first will be rendered.` )
119+ if ( __DEV__ ) {
120+ warning ( true , `Found duplicate "${ String ( slotKey ) } " slot. Only the first will be rendered.` )
121+ }
96122 return
97123 }
98124
0 commit comments