1
1
import hoistStatics from 'hoist-non-react-statics'
2
2
import invariant from 'invariant'
3
3
import { Component , createElement } from 'react'
4
- import { polyfill } from 'react-lifecycles-compat'
5
4
6
5
import Subscription from '../utils/Subscription'
7
6
import { storeShape , subscriptionShape } from '../utils/PropTypes'
8
7
9
8
let hotReloadingVersion = 0
9
+ const dummyState = { }
10
10
function noop ( ) { }
11
- function makeUpdater ( sourceSelector , store ) {
12
- return function updater ( props , prevState ) {
13
- try {
14
- const nextProps = sourceSelector ( store . getState ( ) , props )
15
- if ( nextProps !== prevState . props || prevState . error ) {
16
- return {
17
- shouldComponentUpdate : true ,
18
- props : nextProps ,
19
- error : null ,
11
+ function makeSelectorStateful ( sourceSelector , store ) {
12
+ // wrap the selector in an object that tracks its results between runs.
13
+ const selector = {
14
+ run : function runComponentSelector ( props ) {
15
+ try {
16
+ const nextProps = sourceSelector ( store . getState ( ) , props )
17
+ if ( nextProps !== selector . props || selector . error ) {
18
+ selector . shouldComponentUpdate = true
19
+ selector . props = nextProps
20
+ selector . error = null
20
21
}
21
- }
22
- return {
23
- shouldComponentUpdate : false ,
24
- }
25
- } catch ( error ) {
26
- return {
27
- shouldComponentUpdate : true ,
28
- error,
22
+ } catch ( error ) {
23
+ selector . shouldComponentUpdate = true
24
+ selector . error = error
29
25
}
30
26
}
31
27
}
28
+
29
+ return selector
32
30
}
33
31
34
32
export default function connectAdvanced (
@@ -88,10 +86,6 @@ export default function connectAdvanced(
88
86
[ subscriptionKey ] : subscriptionShape ,
89
87
}
90
88
91
- function getDerivedStateFromProps ( nextProps , prevState ) {
92
- return prevState . updater ( nextProps , prevState )
93
- }
94
-
95
89
return function wrapWithConnect ( WrappedComponent ) {
96
90
invariant (
97
91
typeof WrappedComponent == 'function' ,
@@ -123,6 +117,7 @@ export default function connectAdvanced(
123
117
super ( props , context )
124
118
125
119
this . version = version
120
+ this . state = { }
126
121
this . renderCount = 0
127
122
this . store = props [ storeKey ] || context [ storeKey ]
128
123
this . propsMode = Boolean ( props [ storeKey ] )
@@ -134,9 +129,7 @@ export default function connectAdvanced(
134
129
`or explicitly pass "${ storeKey } " as a prop to "${ displayName } ".`
135
130
)
136
131
137
- this . state = {
138
- updater : this . createUpdater ( )
139
- }
132
+ this . initSelector ( )
140
133
this . initSubscription ( )
141
134
}
142
135
@@ -159,19 +152,25 @@ export default function connectAdvanced(
159
152
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
160
153
// re-render.
161
154
this . subscription . trySubscribe ( )
162
- this . runUpdater ( )
155
+ this . selector . run ( this . props )
156
+ if ( this . selector . shouldComponentUpdate ) this . forceUpdate ( )
157
+ }
158
+
159
+ componentWillReceiveProps ( nextProps ) {
160
+ this . selector . run ( nextProps )
163
161
}
164
162
165
- shouldComponentUpdate ( _ , nextState ) {
166
- return nextState . shouldComponentUpdate
163
+ shouldComponentUpdate ( ) {
164
+ return this . selector . shouldComponentUpdate
167
165
}
168
166
169
167
componentWillUnmount ( ) {
170
168
if ( this . subscription ) this . subscription . tryUnsubscribe ( )
171
169
this . subscription = null
172
170
this . notifyNestedSubs = noop
173
171
this . store = null
174
- this . isUnmounted = true
172
+ this . selector . run = noop
173
+ this . selector . shouldComponentUpdate = false
175
174
}
176
175
177
176
getWrappedInstance ( ) {
@@ -186,17 +185,10 @@ export default function connectAdvanced(
186
185
this . wrappedInstance = ref
187
186
}
188
187
189
- createUpdater ( ) {
188
+ initSelector ( ) {
190
189
const sourceSelector = selectorFactory ( this . store . dispatch , selectorFactoryOptions )
191
- return makeUpdater ( sourceSelector , this . store )
192
- }
193
-
194
- runUpdater ( callback = noop ) {
195
- if ( this . isUnmounted ) {
196
- return
197
- }
198
-
199
- this . setState ( prevState => prevState . updater ( this . props , prevState ) , callback )
190
+ this . selector = makeSelectorStateful ( sourceSelector , this . store )
191
+ this . selector . run ( this . props )
200
192
}
201
193
202
194
initSubscription ( ) {
@@ -217,7 +209,24 @@ export default function connectAdvanced(
217
209
}
218
210
219
211
onStateChange ( ) {
220
- this . runUpdater ( this . notifyNestedSubs )
212
+ this . selector . run ( this . props )
213
+
214
+ if ( ! this . selector . shouldComponentUpdate ) {
215
+ this . notifyNestedSubs ( )
216
+ } else {
217
+ this . componentDidUpdate = this . notifyNestedSubsOnComponentDidUpdate
218
+ this . setState ( dummyState )
219
+ }
220
+ }
221
+
222
+ notifyNestedSubsOnComponentDidUpdate ( ) {
223
+ // `componentDidUpdate` is conditionally implemented when `onStateChange` determines it
224
+ // needs to notify nested subs. Once called, it unimplements itself until further state
225
+ // changes occur. Doing it this way vs having a permanent `componentDidUpdate` that does
226
+ // a boolean check every time avoids an extra method call most of the time, resulting
227
+ // in some perf boost.
228
+ this . componentDidUpdate = undefined
229
+ this . notifyNestedSubs ( )
221
230
}
222
231
223
232
isSubscribed ( ) {
@@ -238,10 +247,13 @@ export default function connectAdvanced(
238
247
}
239
248
240
249
render ( ) {
241
- if ( this . state . error ) {
242
- throw this . state . error
250
+ const selector = this . selector
251
+ selector . shouldComponentUpdate = false
252
+
253
+ if ( selector . error ) {
254
+ throw selector . error
243
255
} else {
244
- return createElement ( WrappedComponent , this . addExtraProps ( this . state . props ) )
256
+ return createElement ( WrappedComponent , this . addExtraProps ( selector . props ) )
245
257
}
246
258
}
247
259
}
@@ -251,13 +263,13 @@ export default function connectAdvanced(
251
263
Connect . childContextTypes = childContextTypes
252
264
Connect . contextTypes = contextTypes
253
265
Connect . propTypes = contextTypes
254
- Connect . getDerivedStateFromProps = getDerivedStateFromProps
255
266
256
267
if ( process . env . NODE_ENV !== 'production' ) {
257
- Connect . prototype . componentDidUpdate = function componentDidUpdate ( ) {
268
+ Connect . prototype . componentWillUpdate = function componentWillUpdate ( ) {
258
269
// We are hot reloading!
259
270
if ( this . version !== version ) {
260
271
this . version = version
272
+ this . initSelector ( )
261
273
262
274
// If any connected descendants don't hot reload (and resubscribe in the process), their
263
275
// listeners will be lost when we unsubscribe. Unfortunately, by copying over all
@@ -275,16 +287,10 @@ export default function connectAdvanced(
275
287
this . subscription . trySubscribe ( )
276
288
oldListeners . forEach ( listener => this . subscription . listeners . subscribe ( listener ) )
277
289
}
278
-
279
- const updater = this . createUpdater ( )
280
- this . setState ( { updater} )
281
- this . runUpdater ( )
282
290
}
283
291
}
284
292
}
285
293
286
- polyfill ( Connect )
287
-
288
294
return hoistStatics ( Connect , WrappedComponent )
289
295
}
290
296
}
0 commit comments