-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
Copy pathselectorFactory.ts
242 lines (207 loc) · 7.5 KB
/
selectorFactory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import type { Dispatch, Action } from 'redux'
import type { ComponentType } from 'react'
import verifySubselectors from './verifySubselectors'
import type { EqualityFn, ExtendedEqualityFn } from '../types'
export type SelectorFactory<S, TProps, TOwnProps, TFactoryOptions> = (
dispatch: Dispatch<Action<string>>,
factoryOptions: TFactoryOptions,
) => Selector<S, TProps, TOwnProps>
export type Selector<S, TProps, TOwnProps = null> = TOwnProps extends
| null
| undefined
? (state: S) => TProps
: (state: S, ownProps: TOwnProps) => TProps
export type MapStateToProps<TStateProps, TOwnProps, State> = (
state: State,
ownProps: TOwnProps,
) => TStateProps
export type MapStateToPropsFactory<TStateProps, TOwnProps, State> = (
initialState: State,
ownProps: TOwnProps,
) => MapStateToProps<TStateProps, TOwnProps, State>
export type MapStateToPropsParam<TStateProps, TOwnProps, State> =
| MapStateToPropsFactory<TStateProps, TOwnProps, State>
| MapStateToProps<TStateProps, TOwnProps, State>
| null
| undefined
export type MapDispatchToPropsFunction<TDispatchProps, TOwnProps> = (
dispatch: Dispatch<Action<string>>,
ownProps: TOwnProps,
) => TDispatchProps
export type MapDispatchToProps<TDispatchProps, TOwnProps> =
| MapDispatchToPropsFunction<TDispatchProps, TOwnProps>
| TDispatchProps
export type MapDispatchToPropsFactory<TDispatchProps, TOwnProps> = (
dispatch: Dispatch<Action<string>>,
ownProps: TOwnProps,
) => MapDispatchToPropsFunction<TDispatchProps, TOwnProps>
export type MapDispatchToPropsParam<TDispatchProps, TOwnProps> =
| MapDispatchToPropsFactory<TDispatchProps, TOwnProps>
| MapDispatchToProps<TDispatchProps, TOwnProps>
export type MapDispatchToPropsNonObject<TDispatchProps, TOwnProps> =
| MapDispatchToPropsFactory<TDispatchProps, TOwnProps>
| MapDispatchToPropsFunction<TDispatchProps, TOwnProps>
export type MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps> = (
stateProps: TStateProps,
dispatchProps: TDispatchProps,
ownProps: TOwnProps,
) => TMergedProps
interface PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State> {
readonly areStatesEqual: ExtendedEqualityFn<State, TOwnProps>
readonly areStatePropsEqual: EqualityFn<TStateProps>
readonly areOwnPropsEqual: EqualityFn<TOwnProps>
}
function pureFinalPropsSelectorFactory<
TStateProps,
TOwnProps,
TDispatchProps,
TMergedProps,
State,
>(
mapStateToProps: WrappedMapStateToProps<TStateProps, TOwnProps, State>,
mapDispatchToProps: WrappedMapDispatchToProps<TDispatchProps, TOwnProps>,
mergeProps: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
dispatch: Dispatch<Action<string>>,
{
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
}: PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State>,
) {
let hasRunAtLeastOnce = false
let state: State
let ownProps: TOwnProps
let stateProps: TStateProps
let dispatchProps: TDispatchProps
let mergedProps: TMergedProps
function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) {
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
function handleNewPropsAndNewState() {
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewProps() {
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) {
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
const stateChanged = !areStatesEqual(
nextState,
state,
nextOwnProps,
ownProps,
)
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(
nextState: State,
nextOwnProps: TOwnProps,
) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
interface WrappedMapStateToProps<TStateProps, TOwnProps, State> {
(state: State, ownProps: TOwnProps): TStateProps
readonly dependsOnOwnProps: boolean
}
interface WrappedMapDispatchToProps<TDispatchProps, TOwnProps> {
(dispatch: Dispatch<Action<string>>, ownProps: TOwnProps): TDispatchProps
readonly dependsOnOwnProps: boolean
}
export interface InitOptions<TStateProps, TOwnProps, TMergedProps, State>
extends PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State> {
readonly shouldHandleStateChanges: boolean
readonly displayName: string
readonly wrappedComponentName: string
readonly WrappedComponent: ComponentType<TOwnProps>
readonly areMergedPropsEqual: EqualityFn<TMergedProps>
}
export interface SelectorFactoryOptions<
TStateProps,
TOwnProps,
TDispatchProps,
TMergedProps,
State,
> extends InitOptions<TStateProps, TOwnProps, TMergedProps, State> {
readonly initMapStateToProps: (
dispatch: Dispatch<Action<string>>,
options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>,
) => WrappedMapStateToProps<TStateProps, TOwnProps, State>
readonly initMapDispatchToProps: (
dispatch: Dispatch<Action<string>>,
options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>,
) => WrappedMapDispatchToProps<TDispatchProps, TOwnProps>
readonly initMergeProps: (
dispatch: Dispatch<Action<string>>,
options: InitOptions<TStateProps, TOwnProps, TMergedProps, State>,
) => MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>
}
// TODO: Add more comments
// The selector returned by selectorFactory will memoize its results,
// allowing connect's shouldComponentUpdate to return false if final
// props have not changed.
export default function finalPropsSelectorFactory<
TStateProps,
TOwnProps,
TDispatchProps,
TMergedProps,
State,
>(
dispatch: Dispatch<Action<string>>,
{
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
...options
}: SelectorFactoryOptions<
TStateProps,
TOwnProps,
TDispatchProps,
TMergedProps,
State
>,
) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps)
}
return pureFinalPropsSelectorFactory<
TStateProps,
TOwnProps,
TDispatchProps,
TMergedProps,
State
>(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options)
}