Skip to content

Commit 7c1bf33

Browse files
committed
Copy changes from original v6 experiment branch
1 parent 3349bd0 commit 7c1bf33

File tree

5 files changed

+123
-225
lines changed

5 files changed

+123
-225
lines changed

src/components/Provider.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { Component, Children } from 'react'
1+
import React, { Component, Children } from 'react'
22
import PropTypes from 'prop-types'
3-
import { storeShape, subscriptionShape } from '../utils/PropTypes'
3+
import { storeShape } from '../utils/PropTypes'
44
import warning from '../utils/warning'
55

6+
import {ReactReduxContext} from "./context";
7+
68
let didWarnAboutReceivingStore = false
79
function warnAboutReceivingStore() {
810
if (didWarnAboutReceivingStore) {
@@ -19,40 +21,52 @@ function warnAboutReceivingStore() {
1921
)
2022
}
2123

22-
export function createProvider(storeKey = 'store') {
23-
const subscriptionKey = `${storeKey}Subscription`
24+
export function createProvider(storeKey = 'store', subKey) {
2425

2526
class Provider extends Component {
26-
getChildContext() {
27-
return { [storeKey]: this[storeKey], [subscriptionKey]: null }
27+
28+
constructor(props) {
29+
super(props)
30+
31+
const {store} = props;
32+
33+
this.state = {
34+
storeState : store.getState(),
35+
dispatch : store.dispatch,
36+
};
2837
}
2938

30-
constructor(props, context) {
31-
super(props, context)
32-
this[storeKey] = props.store;
39+
componentDidMount() {
40+
const {store} = this.props;
41+
42+
// TODO What about any actions that might have been dispatched between ctor and cDM?
43+
this.unsubscribe = store.subscribe( () => {
44+
this.setState({storeState : store.getState()});
45+
});
3346
}
3447

3548
render() {
36-
return Children.only(this.props.children)
49+
return (
50+
<ReactReduxContext.Provider value={this.state}>
51+
{Children.only(this.props.children)}
52+
</ReactReduxContext.Provider>
53+
);
3754
}
3855
}
3956

4057
if (process.env.NODE_ENV !== 'production') {
41-
Provider.prototype.componentDidUpdate = function () {
42-
if (this[storeKey] !== this.props.store) {
58+
Provider.prototype.componentWillReceiveProps = function (nextProps) {
59+
if (this[storeKey] !== nextProps.store) {
4360
warnAboutReceivingStore()
4461
}
4562
}
4663
}
4764

65+
4866
Provider.propTypes = {
4967
store: storeShape.isRequired,
5068
children: PropTypes.element.isRequired,
5169
}
52-
Provider.childContextTypes = {
53-
[storeKey]: storeShape.isRequired,
54-
[subscriptionKey]: subscriptionShape,
55-
}
5670

5771
return Provider
5872
}

src/components/connectAdvanced.js

Lines changed: 90 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
import hoistStatics from 'hoist-non-react-statics'
22
import invariant from 'invariant'
3-
import { Component, createElement } from 'react'
4-
import { polyfill } from 'react-lifecycles-compat'
3+
import React, { Component, createElement } from 'react'
54

6-
import Subscription from '../utils/Subscription'
7-
import { storeShape, subscriptionShape } from '../utils/PropTypes'
5+
import {ReactReduxContext} from "./context";
6+
import { storeShape } from '../utils/PropTypes'
87

98
let hotReloadingVersion = 0
9+
const dummyState = {}
1010
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) {
12+
// wrap the selector in an object that tracks its results between runs.
13+
const selector = {
14+
run: function runComponentSelector(props, storeState) {
15+
try {
16+
const nextProps = sourceSelector(storeState, 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

3432
export default function connectAdvanced(
@@ -77,24 +75,12 @@ export default function connectAdvanced(
7775
...connectOptions
7876
} = {}
7977
) {
80-
const subscriptionKey = storeKey + 'Subscription'
8178
const version = hotReloadingVersion++
8279

83-
const contextTypes = {
84-
[storeKey]: storeShape,
85-
[subscriptionKey]: subscriptionShape,
86-
}
87-
const childContextTypes = {
88-
[subscriptionKey]: subscriptionShape,
89-
}
90-
91-
function getDerivedStateFromProps(nextProps, prevState) {
92-
return prevState.updater(nextProps, prevState)
93-
}
9480

9581
return function wrapWithConnect(WrappedComponent) {
9682
invariant(
97-
typeof WrappedComponent == 'function',
83+
typeof WrappedComponent === 'function',
9884
`You must pass a component to the function returned by ` +
9985
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
10086
)
@@ -119,34 +105,25 @@ export default function connectAdvanced(
119105
}
120106

121107
class Connect extends Component {
122-
constructor(props, context) {
123-
super(props, context)
108+
constructor(props) {
109+
super(props)
124110

125111
this.version = version
126112
this.renderCount = 0
127-
this.store = props[storeKey] || context[storeKey]
128-
this.propsMode = Boolean(props[storeKey])
113+
this.storeState = null;
114+
115+
129116
this.setWrappedInstance = this.setWrappedInstance.bind(this)
117+
this.renderChild = this.renderChild.bind(this);
130118

119+
// TODO How do we express the invariant of needing a Provider when it's used in render()?
120+
/*
131121
invariant(this.store,
132122
`Could not find "${storeKey}" in either the context or props of ` +
133123
`"${displayName}". Either wrap the root component in a <Provider>, ` +
134124
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
135125
)
136-
137-
this.state = {
138-
updater: this.createUpdater()
139-
}
140-
this.initSubscription()
141-
}
142-
143-
getChildContext() {
144-
// If this component received store from props, its subscription should be transparent
145-
// to any descendants receiving store+subscription from context; it passes along
146-
// subscription passed to it. Otherwise, it shadows the parent subscription, which allows
147-
// Connect to control ordering of notifications to flow top-down.
148-
const subscription = this.propsMode ? null : this.subscription
149-
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
126+
*/
150127
}
151128

152129
componentDidMount() {
@@ -158,20 +135,24 @@ export default function connectAdvanced(
158135
// To handle the case where a child component may have triggered a state change by
159136
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
160137
// re-render.
161-
this.subscription.trySubscribe()
162-
this.runUpdater()
138+
this.selector.run(this.props, this.storeState);
139+
if (this.selector.shouldComponentUpdate) this.forceUpdate()
140+
}
141+
142+
143+
UNSAFE_componentWillReceiveProps(nextProps) {
144+
// TODO Do we still want/need to implement sCU / cWRP now?
145+
this.selector.run(nextProps, this.storeState);
163146
}
164147

165-
shouldComponentUpdate(_, nextState) {
166-
return nextState.shouldComponentUpdate
148+
shouldComponentUpdate() {
149+
return this.selector.shouldComponentUpdate
167150
}
168151

152+
169153
componentWillUnmount() {
170-
if (this.subscription) this.subscription.tryUnsubscribe()
171-
this.subscription = null
172-
this.notifyNestedSubs = noop
173-
this.store = null
174-
this.isUnmounted = true
154+
this.selector.run = noop
155+
this.selector.shouldComponentUpdate = false
175156
}
176157

177158
getWrappedInstance() {
@@ -186,78 +167,77 @@ export default function connectAdvanced(
186167
this.wrappedInstance = ref
187168
}
188169

189-
createUpdater() {
190-
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)
200-
}
201-
202-
initSubscription() {
203-
if (!shouldHandleStateChanges) return
204-
205-
// parentSub's source should match where store came from: props vs. context. A component
206-
// connected to the store via props shouldn't use subscription from context, or vice versa.
207-
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
208-
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
209-
210-
// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
211-
// the middle of the notification loop, where `this.subscription` will then be null. An
212-
// extra null check every change can be avoided by copying the method onto `this` and then
213-
// replacing it with a no-op on unmount. This can probably be avoided if Subscription's
214-
// listeners logic is changed to not call listeners that have been unsubscribed in the
215-
// middle of the notification loop.
216-
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
217-
}
218-
219-
onStateChange() {
220-
this.runUpdater(this.notifyNestedSubs)
221-
}
222-
223-
isSubscribed() {
224-
return Boolean(this.subscription) && this.subscription.isSubscribed()
170+
initSelector(dispatch, storeState) {
171+
const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions)
172+
this.selector = makeSelectorStateful(sourceSelector)
173+
this.selector.run(this.props, storeState);
225174
}
226175

227176
addExtraProps(props) {
228-
if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
177+
if (!withRef && !renderCountProp) return props;
178+
229179
// make a shallow copy so that fields added don't leak to the original selector.
230180
// this is especially important for 'ref' since that's a reference back to the component
231181
// instance. a singleton memoized selector would then be holding a reference to the
232182
// instance, preventing the instance from being garbage collected, and that would be bad
233183
const withExtras = { ...props }
234184
if (withRef) withExtras.ref = this.setWrappedInstance
235185
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
236-
if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
186+
237187
return withExtras
238188
}
239189

190+
renderChild(providerValue) {
191+
const {storeState, dispatch} = providerValue;
192+
193+
this.storeState = storeState;
194+
195+
if(this.selector) {
196+
this.selector.run(this.props, storeState);
197+
}
198+
else {
199+
this.initSelector(dispatch, storeState);
200+
}
201+
202+
if (this.selector.error) {
203+
// TODO This will unmount the whole tree now that we're throwing in render. Good idea?
204+
// TODO Related: https://github.com/reactjs/react-redux/issues/802
205+
throw this.selector.error
206+
}
207+
else if(this.selector.shouldComponentUpdate) {
208+
//console.log(`Re-rendering component (${displayName})`, this.selector.props);
209+
this.selector.shouldComponentUpdate = false;
210+
this.renderedElement = createElement(WrappedComponent, this.addExtraProps(this.selector.props));
211+
}
212+
else {
213+
//console.log(`Returning existing render result (${displayName})`, this.props)
214+
}
215+
216+
return this.renderedElement;
217+
}
218+
240219
render() {
241-
if (this.state.error) {
242-
throw this.state.error
243-
} else {
244-
return createElement(WrappedComponent, this.addExtraProps(this.state.props))
245-
}
220+
return (
221+
<ReactReduxContext.Consumer>
222+
{this.renderChild}
223+
</ReactReduxContext.Consumer>
224+
)
246225
}
247226
}
248227

249228
Connect.WrappedComponent = WrappedComponent
250229
Connect.displayName = displayName
251-
Connect.childContextTypes = childContextTypes
252-
Connect.contextTypes = contextTypes
253-
Connect.propTypes = contextTypes
254-
Connect.getDerivedStateFromProps = getDerivedStateFromProps
230+
// TODO We're losing the ability to add a store as a prop. Not sure there's anything we can do about that.
231+
//Connect.propTypes = contextTypes
255232

233+
// TODO With connect no longer managing subscriptions, I _think_ is is all unneeded
234+
/*
256235
if (process.env.NODE_ENV !== 'production') {
257-
Connect.prototype.componentDidUpdate = function componentDidUpdate() {
236+
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
258237
// We are hot reloading!
259238
if (this.version !== version) {
260239
this.version = version
240+
this.initSelector()
261241
262242
// If any connected descendants don't hot reload (and resubscribe in the process), their
263243
// listeners will be lost when we unsubscribe. Unfortunately, by copying over all
@@ -275,15 +255,10 @@ export default function connectAdvanced(
275255
this.subscription.trySubscribe()
276256
oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
277257
}
278-
279-
const updater = this.createUpdater()
280-
this.setState({updater})
281-
this.runUpdater()
282258
}
283259
}
284260
}
285-
286-
polyfill(Connect)
261+
*/
287262

288263
return hoistStatics(Connect, WrappedComponent)
289264
}

src/components/context.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import React from "react";
2+
3+
export const ReactReduxContext = React.createContext(null);

src/utils/PropTypes.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import PropTypes from 'prop-types'
22

3-
export const subscriptionShape = PropTypes.shape({
4-
trySubscribe: PropTypes.func.isRequired,
5-
tryUnsubscribe: PropTypes.func.isRequired,
6-
notifyNestedSubs: PropTypes.func.isRequired,
7-
isSubscribed: PropTypes.func.isRequired,
8-
})
9-
103
export const storeShape = PropTypes.shape({
114
subscribe: PropTypes.func.isRequired,
125
dispatch: PropTypes.func.isRequired,

0 commit comments

Comments
 (0)