Skip to content

Commit 4e654e2

Browse files
committed
Copy changes from original v6 experiment branch
1 parent 47facdb commit 4e654e2

File tree

5 files changed

+99
-212
lines changed

5 files changed

+99
-212
lines changed

src/components/Provider.js

Lines changed: 28 additions & 14 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,21 +21,36 @@ 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

@@ -45,14 +62,11 @@ export function createProvider(storeKey = 'store') {
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: 68 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import hoistStatics from 'hoist-non-react-statics'
22
import invariant from 'invariant'
3-
import { Component, createElement } from 'react'
3+
import React, { Component, createElement } from 'react'
44

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

88
let hotReloadingVersion = 0
99
const dummyState = {}
1010
function noop() {}
11-
function makeSelectorStateful(sourceSelector, store) {
11+
function makeSelectorStateful(sourceSelector) {
1212
// wrap the selector in an object that tracks its results between runs.
1313
const selector = {
14-
run: function runComponentSelector(props) {
14+
run: function runComponentSelector(props, storeState) {
1515
try {
16-
const nextProps = sourceSelector(store.getState(), props)
16+
const nextProps = sourceSelector(storeState, props)
1717
if (nextProps !== selector.props || selector.error) {
1818
selector.shouldComponentUpdate = true
1919
selector.props = nextProps
@@ -75,20 +75,12 @@ export default function connectAdvanced(
7575
...connectOptions
7676
} = {}
7777
) {
78-
const subscriptionKey = storeKey + 'Subscription'
7978
const version = hotReloadingVersion++
8079

81-
const contextTypes = {
82-
[storeKey]: storeShape,
83-
[subscriptionKey]: subscriptionShape,
84-
}
85-
const childContextTypes = {
86-
[subscriptionKey]: subscriptionShape,
87-
}
8880

8981
return function wrapWithConnect(WrappedComponent) {
9082
invariant(
91-
typeof WrappedComponent == 'function',
83+
typeof WrappedComponent === 'function',
9284
`You must pass a component to the function returned by ` +
9385
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
9486
)
@@ -112,37 +104,26 @@ export default function connectAdvanced(
112104
WrappedComponent
113105
}
114106

115-
// TODO Actually fix our use of componentWillReceiveProps
116-
/* eslint-disable react/no-deprecated */
117-
118107
class Connect extends Component {
119-
constructor(props, context) {
120-
super(props, context)
108+
constructor(props) {
109+
super(props)
121110

122111
this.version = version
123-
this.state = {}
124112
this.renderCount = 0
125-
this.store = props[storeKey] || context[storeKey]
126-
this.propsMode = Boolean(props[storeKey])
113+
this.storeState = null;
114+
115+
127116
this.setWrappedInstance = this.setWrappedInstance.bind(this)
117+
this.renderChild = this.renderChild.bind(this);
128118

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

148129
componentDidMount() {
@@ -154,24 +135,22 @@ export default function connectAdvanced(
154135
// To handle the case where a child component may have triggered a state change by
155136
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
156137
// re-render.
157-
this.subscription.trySubscribe()
158-
this.selector.run(this.props)
138+
this.selector.run(this.props, this.storeState);
159139
if (this.selector.shouldComponentUpdate) this.forceUpdate()
160140
}
161141

162-
componentWillReceiveProps(nextProps) {
163-
this.selector.run(nextProps)
142+
143+
UNSAFE_componentWillReceiveProps(nextProps) {
144+
// TODO Do we still want/need to implement sCU / cWRP now?
145+
this.selector.run(nextProps, this.storeState);
164146
}
165147

166148
shouldComponentUpdate() {
167149
return this.selector.shouldComponentUpdate
168150
}
169151

152+
170153
componentWillUnmount() {
171-
if (this.subscription) this.subscription.tryUnsubscribe()
172-
this.subscription = null
173-
this.notifyNestedSubs = noop
174-
this.store = null
175154
this.selector.run = noop
176155
this.selector.shouldComponentUpdate = false
177156
}
@@ -188,87 +167,71 @@ export default function connectAdvanced(
188167
this.wrappedInstance = ref
189168
}
190169

191-
initSelector() {
192-
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
193-
this.selector = makeSelectorStateful(sourceSelector, this.store)
194-
this.selector.run(this.props)
195-
}
196-
197-
initSubscription() {
198-
if (!shouldHandleStateChanges) return
199-
200-
// parentSub's source should match where store came from: props vs. context. A component
201-
// connected to the store via props shouldn't use subscription from context, or vice versa.
202-
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
203-
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
204-
205-
// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
206-
// the middle of the notification loop, where `this.subscription` will then be null. An
207-
// extra null check every change can be avoided by copying the method onto `this` and then
208-
// replacing it with a no-op on unmount. This can probably be avoided if Subscription's
209-
// listeners logic is changed to not call listeners that have been unsubscribed in the
210-
// middle of the notification loop.
211-
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
212-
}
213-
214-
onStateChange() {
215-
this.selector.run(this.props)
216-
217-
if (!this.selector.shouldComponentUpdate) {
218-
this.notifyNestedSubs()
219-
} else {
220-
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
221-
this.setState(dummyState)
222-
}
223-
}
224-
225-
notifyNestedSubsOnComponentDidUpdate() {
226-
// `componentDidUpdate` is conditionally implemented when `onStateChange` determines it
227-
// needs to notify nested subs. Once called, it unimplements itself until further state
228-
// changes occur. Doing it this way vs having a permanent `componentDidUpdate` that does
229-
// a boolean check every time avoids an extra method call most of the time, resulting
230-
// in some perf boost.
231-
this.componentDidUpdate = undefined
232-
this.notifyNestedSubs()
233-
}
234-
235-
isSubscribed() {
236-
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);
237174
}
238175

239176
addExtraProps(props) {
240-
if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
177+
if (!withRef && !renderCountProp) return props;
178+
241179
// make a shallow copy so that fields added don't leak to the original selector.
242180
// this is especially important for 'ref' since that's a reference back to the component
243181
// instance. a singleton memoized selector would then be holding a reference to the
244182
// instance, preventing the instance from being garbage collected, and that would be bad
245183
const withExtras = { ...props }
246184
if (withRef) withExtras.ref = this.setWrappedInstance
247185
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
248-
if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
186+
249187
return withExtras
250188
}
251189

252-
render() {
253-
const selector = this.selector
254-
selector.shouldComponentUpdate = false
190+
renderChild(providerValue) {
191+
const {storeState, dispatch} = providerValue;
255192

256-
if (selector.error) {
257-
throw selector.error
258-
} else {
259-
return createElement(WrappedComponent, this.addExtraProps(selector.props))
260-
}
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;
261217
}
262-
}
263218

264-
/* eslint-enable react/no-deprecated */
219+
render() {
220+
return (
221+
<ReactReduxContext.Consumer>
222+
{this.renderChild}
223+
</ReactReduxContext.Consumer>
224+
)
225+
}
226+
}
265227

266228
Connect.WrappedComponent = WrappedComponent
267229
Connect.displayName = displayName
268-
Connect.childContextTypes = childContextTypes
269-
Connect.contextTypes = contextTypes
270-
Connect.propTypes = contextTypes
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
271232

233+
// TODO With connect no longer managing subscriptions, I _think_ is is all unneeded
234+
/*
272235
if (process.env.NODE_ENV !== 'production') {
273236
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
274237
// We are hot reloading!
@@ -295,6 +258,7 @@ export default function connectAdvanced(
295258
}
296259
}
297260
}
261+
*/
298262

299263
return hoistStatics(Connect, WrappedComponent)
300264
}

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)