1
1
import type { AnyAction , Reducer } from 'redux'
2
- import { createNextState } from '.'
3
2
import type {
4
3
ActionCreatorWithoutPayload ,
5
4
PayloadAction ,
@@ -16,8 +15,9 @@ import type {
16
15
import { createReducer , NotFunction } from './createReducer'
17
16
import type { ActionReducerMapBuilder } from './mapBuilders'
18
17
import { executeReducerBuilderCallback } from './mapBuilders'
19
- import type { NoInfer } from './tsHelpers'
18
+ import type { Id , NoInfer , Tail } from './tsHelpers'
20
19
import { freezeDraftable } from './utils'
20
+ import type { CombinedSliceReducer } from './combineSlices'
21
21
22
22
let hasWarnedAboutObjectNotation = false
23
23
@@ -38,7 +38,8 @@ export type SliceActionCreator<P> = PayloadActionCreator<P>
38
38
export interface Slice <
39
39
State = any ,
40
40
CaseReducers extends SliceCaseReducers < State > = SliceCaseReducers < State > ,
41
- Name extends string = string
41
+ Name extends string = string ,
42
+ Selectors extends SliceSelectors < State > = { }
42
43
> {
43
44
/**
44
45
* The slice name.
@@ -67,6 +68,49 @@ export interface Slice<
67
68
* If a lazy state initializer was provided, it will be called and a fresh value returned.
68
69
*/
69
70
getInitialState : ( ) => State
71
+
72
+ getSelectors ( ) : Id < SliceDefinedSelectors < State , Selectors , State > >
73
+
74
+ getSelectors < RootState > (
75
+ selectState : ( rootState : RootState ) => State
76
+ ) : Id < SliceDefinedSelectors < State , Selectors , RootState > >
77
+
78
+ selectors : Id < SliceDefinedSelectors < State , Selectors , { [ K in Name ] : State } > >
79
+
80
+ injectInto (
81
+ combinedReducer : CombinedSliceReducer < any >
82
+ ) : InjectedSlice < State , CaseReducers , Name , Selectors >
83
+ }
84
+
85
+ interface InjectedSlice <
86
+ State = any ,
87
+ CaseReducers extends SliceCaseReducers < State > = SliceCaseReducers < State > ,
88
+ Name extends string = string ,
89
+ Selectors extends SliceSelectors < State > = { }
90
+ > extends Omit <
91
+ Slice < State , CaseReducers , Name , Selectors > ,
92
+ 'getSelectors' | 'selectors'
93
+ > {
94
+ getSelectors ( ) : Id < SliceDefinedSelectors < State , Selectors , State | undefined > >
95
+
96
+ getSelectors < RootState > (
97
+ selectState : ( rootState : RootState ) => State | undefined
98
+ ) : Id < SliceDefinedSelectors < State , Selectors , RootState > >
99
+
100
+ selectors : Id <
101
+ SliceDefinedSelectors < State , Selectors , { [ K in Name ] ?: State | undefined } >
102
+ >
103
+ }
104
+
105
+ type SliceDefinedSelectors <
106
+ State ,
107
+ Selectors extends SliceSelectors < State > ,
108
+ RootState
109
+ > = {
110
+ [ K in keyof Selectors as [ string ] extends [ K ] ? never : K ] : (
111
+ rootState : RootState ,
112
+ ...args : Tail < Parameters < Selectors [ K ] > >
113
+ ) => ReturnType < Selectors [ K ] >
70
114
}
71
115
72
116
/**
@@ -77,7 +121,8 @@ export interface Slice<
77
121
export interface CreateSliceOptions <
78
122
State = any ,
79
123
CR extends SliceCaseReducers < State > = SliceCaseReducers < State > ,
80
- Name extends string = string
124
+ Name extends string = string ,
125
+ Selectors extends SliceSelectors < State > = Record < never , never >
81
126
> {
82
127
/**
83
128
* The slice's name. Used to namespace the generated action types.
@@ -142,6 +187,8 @@ createSlice({
142
187
```
143
188
*/
144
189
extraReducers ?: ( builder : ActionReducerMapBuilder < NoInfer < State > > ) => void
190
+
191
+ selectors ?: Selectors
145
192
}
146
193
147
194
/**
@@ -165,6 +212,10 @@ export type SliceCaseReducers<State> = {
165
212
| CaseReducerWithPrepare < State , PayloadAction < any , string , any , any > >
166
213
}
167
214
215
+ export type SliceSelectors < State > = {
216
+ [ K : string ] : ( sliceState : State , ...args : any [ ] ) => any
217
+ }
218
+
168
219
type SliceActionType <
169
220
SliceName extends string ,
170
221
ActionName extends keyof any
@@ -271,10 +322,11 @@ function getType(slice: string, actionKey: string): string {
271
322
export function createSlice <
272
323
State ,
273
324
CaseReducers extends SliceCaseReducers < State > ,
274
- Name extends string = string
325
+ Name extends string ,
326
+ Selectors extends SliceSelectors < State >
275
327
> (
276
- options : CreateSliceOptions < State , CaseReducers , Name >
277
- ) : Slice < State , CaseReducers , Name > {
328
+ options : CreateSliceOptions < State , CaseReducers , Name , Selectors >
329
+ ) : Slice < State , CaseReducers , Name , Selectors > {
278
330
const { name } = options
279
331
if ( ! name ) {
280
332
throw new Error ( '`name` is a required option for createSlice' )
@@ -357,6 +409,24 @@ export function createSlice<
357
409
} )
358
410
}
359
411
412
+ const defaultSelectSlice = ( rootState : { [ K in Name ] : State } ) =>
413
+ rootState [ name ]
414
+
415
+ const selectSelf = ( state : State ) => state
416
+
417
+ const selectorCache = new WeakMap <
418
+ ( rootState : any ) => State ,
419
+ Record < string , ( rootState : any ) => any >
420
+ > ( )
421
+
422
+ const injectedSelectorCache = new WeakMap <
423
+ CombinedSliceReducer < any > ,
424
+ WeakMap <
425
+ ( rootState : any ) => State | undefined ,
426
+ Record < string , ( rootState : any ) => any >
427
+ >
428
+ > ( )
429
+
360
430
let _reducer : ReducerWithInitialState < State >
361
431
362
432
return {
@@ -373,5 +443,61 @@ export function createSlice<
373
443
374
444
return _reducer . getInitialState ( )
375
445
} ,
446
+ getSelectors ( selectState ?: ( rootState : any ) => State ) {
447
+ if ( selectState ) {
448
+ const cached = selectorCache . get ( selectState )
449
+ if ( cached ) {
450
+ return cached
451
+ }
452
+ const selectors : Record < string , ( rootState : any ) => any > = { }
453
+ for ( const [ name , selector ] of Object . entries (
454
+ options . selectors ?? { }
455
+ ) ) {
456
+ selectors [ name ] = ( rootState : any , ...args : any [ ] ) =>
457
+ selector ( selectState ( rootState ) , ...args )
458
+ }
459
+ selectorCache . set ( selectState , selectors )
460
+ return selectors as any
461
+ } else {
462
+ return options . selectors ?? { }
463
+ }
464
+ } ,
465
+ get selectors ( ) {
466
+ return this . getSelectors ( defaultSelectSlice )
467
+ } ,
468
+ injectInto ( reducer ) {
469
+ reducer . inject ( this )
470
+ let selectorCache = injectedSelectorCache . get ( reducer )
471
+ if ( ! selectorCache ) {
472
+ selectorCache = new WeakMap ( )
473
+ injectedSelectorCache . set ( reducer , selectorCache )
474
+ }
475
+ return {
476
+ ...this ,
477
+ getSelectors (
478
+ selectState : ( rootState : any ) => State | undefined = selectSelf
479
+ ) {
480
+ const cached = selectorCache ! . get ( selectState )
481
+ if ( cached ) {
482
+ return cached
483
+ }
484
+ const selectors : Record < string , ( rootState : any ) => any > = { }
485
+ for ( const [ name , selector ] of Object . entries (
486
+ options . selectors ?? { }
487
+ ) ) {
488
+ selectors [ name ] = ( rootState : any , ...args : any [ ] ) =>
489
+ selector (
490
+ selectState ( rootState ) ?? this . getInitialState ( ) ,
491
+ ...args
492
+ )
493
+ }
494
+ selectorCache ! . set ( selectState , selectors )
495
+ return selectors as any
496
+ } ,
497
+ get selectors ( ) {
498
+ return this . getSelectors ( defaultSelectSlice )
499
+ } ,
500
+ } as any
501
+ } ,
376
502
}
377
503
}
0 commit comments