11import hoistStatics from 'hoist-non-react-statics'
22import invariant from 'invariant'
33import { Component , createElement } from 'react'
4- import { polyfill } from 'react-lifecycles-compat'
54
65import Subscription from '../utils/Subscription'
76import { storeShape , subscriptionShape } from '../utils/PropTypes'
87
98let hotReloadingVersion = 0
9+ const dummyState = { }
1010function 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
2021 }
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
2925 }
3026 }
3127 }
28+
29+ return selector
3230}
3331
3432export default function connectAdvanced (
@@ -88,10 +86,6 @@ export default function connectAdvanced(
8886 [ subscriptionKey ] : subscriptionShape ,
8987 }
9088
91- function getDerivedStateFromProps ( nextProps , prevState ) {
92- return prevState . updater ( nextProps , prevState )
93- }
94-
9589 return function wrapWithConnect ( WrappedComponent ) {
9690 invariant (
9791 typeof WrappedComponent == 'function' ,
@@ -123,6 +117,7 @@ export default function connectAdvanced(
123117 super ( props , context )
124118
125119 this . version = version
120+ this . state = { }
126121 this . renderCount = 0
127122 this . store = props [ storeKey ] || context [ storeKey ]
128123 this . propsMode = Boolean ( props [ storeKey ] )
@@ -134,9 +129,7 @@ export default function connectAdvanced(
134129 `or explicitly pass "${ storeKey } " as a prop to "${ displayName } ".`
135130 )
136131
137- this . state = {
138- updater : this . createUpdater ( )
139- }
132+ this . initSelector ( )
140133 this . initSubscription ( )
141134 }
142135
@@ -159,19 +152,25 @@ export default function connectAdvanced(
159152 // dispatching an action in its componentWillMount, we have to re-run the select and maybe
160153 // re-render.
161154 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 )
163161 }
164162
165- shouldComponentUpdate ( _ , nextState ) {
166- return nextState . shouldComponentUpdate
163+ shouldComponentUpdate ( ) {
164+ return this . selector . shouldComponentUpdate
167165 }
168166
169167 componentWillUnmount ( ) {
170168 if ( this . subscription ) this . subscription . tryUnsubscribe ( )
171169 this . subscription = null
172170 this . notifyNestedSubs = noop
173171 this . store = null
174- this . isUnmounted = true
172+ this . selector . run = noop
173+ this . selector . shouldComponentUpdate = false
175174 }
176175
177176 getWrappedInstance ( ) {
@@ -186,17 +185,10 @@ export default function connectAdvanced(
186185 this . wrappedInstance = ref
187186 }
188187
189- createUpdater ( ) {
188+ initSelector ( ) {
190189 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 )
200192 }
201193
202194 initSubscription ( ) {
@@ -217,7 +209,24 @@ export default function connectAdvanced(
217209 }
218210
219211 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 ( )
221230 }
222231
223232 isSubscribed ( ) {
@@ -238,10 +247,13 @@ export default function connectAdvanced(
238247 }
239248
240249 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
243255 } else {
244- return createElement ( WrappedComponent , this . addExtraProps ( this . state . props ) )
256+ return createElement ( WrappedComponent , this . addExtraProps ( selector . props ) )
245257 }
246258 }
247259 }
@@ -251,13 +263,13 @@ export default function connectAdvanced(
251263 Connect . childContextTypes = childContextTypes
252264 Connect . contextTypes = contextTypes
253265 Connect . propTypes = contextTypes
254- Connect . getDerivedStateFromProps = getDerivedStateFromProps
255266
256267 if ( process . env . NODE_ENV !== 'production' ) {
257- Connect . prototype . componentDidUpdate = function componentDidUpdate ( ) {
268+ Connect . prototype . componentWillUpdate = function componentWillUpdate ( ) {
258269 // We are hot reloading!
259270 if ( this . version !== version ) {
260271 this . version = version
272+ this . initSelector ( )
261273
262274 // If any connected descendants don't hot reload (and resubscribe in the process), their
263275 // listeners will be lost when we unsubscribe. Unfortunately, by copying over all
@@ -275,16 +287,10 @@ export default function connectAdvanced(
275287 this . subscription . trySubscribe ( )
276288 oldListeners . forEach ( listener => this . subscription . listeners . subscribe ( listener ) )
277289 }
278-
279- const updater = this . createUpdater ( )
280- this . setState ( { updater} )
281- this . runUpdater ( )
282290 }
283291 }
284292 }
285293
286- polyfill ( Connect )
287-
288294 return hoistStatics ( Connect , WrappedComponent )
289295 }
290296}
0 commit comments