From 576f3f23b578119cd515b2f75625298b8dbe418c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 9 Mar 2018 17:31:54 -0500 Subject: [PATCH] Hacked-up version of Connect and Provider to use new context API --- src/components/Provider.js | 41 +++++++++- src/components/connectAdvanced.js | 119 ++++++++++++++++++++++++------ src/components/context.js | 3 + 3 files changed, 137 insertions(+), 26 deletions(-) create mode 100644 src/components/context.js diff --git a/src/components/Provider.js b/src/components/Provider.js index ac77a003e..c30bc3b55 100644 --- a/src/components/Provider.js +++ b/src/components/Provider.js @@ -1,8 +1,10 @@ -import { Component, Children } from 'react' +import React, { Component, Children } from 'react' import PropTypes from 'prop-types' import { storeShape, subscriptionShape } from '../utils/PropTypes' import warning from '../utils/warning' +import {ReactReduxContext} from "./context"; + let didWarnAboutReceivingStore = false function warnAboutReceivingStore() { if (didWarnAboutReceivingStore) { @@ -20,20 +22,48 @@ function warnAboutReceivingStore() { } export function createProvider(storeKey = 'store', subKey) { - const subscriptionKey = subKey || `${storeKey}Subscription` + //const subscriptionKey = subKey || `${storeKey}Subscription` class Provider extends Component { + /* getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } + */ constructor(props, context) { super(props, context) - this[storeKey] = props.store; + //this[storeKey] = props.store; + + const {store} = props; + + if(!store || !store.getState || !store.dispatch) { + throw new Error("Must pass a valid Redux store as a prop to Provider"); + } + + this.state = { + storeState : store.getState(), + dispatch : store.dispatch, + }; + } + + componentDidMount() { + const {store} = this.props; + + this.unsubscribe = store.subscribe( () => { + console.log("Provider subscription running"); + this.setState({storeState : store.getState()}); + }); } render() { - return Children.only(this.props.children) + console.log("Provider re-rendering"); + + return ( + + {Children.only(this.props.children)} + + ); } } @@ -45,14 +75,17 @@ export function createProvider(storeKey = 'store', subKey) { } } + Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } + /* Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, } + */ return Provider } diff --git a/src/components/connectAdvanced.js b/src/components/connectAdvanced.js index 3fff8ee26..ededb86b3 100644 --- a/src/components/connectAdvanced.js +++ b/src/components/connectAdvanced.js @@ -1,19 +1,20 @@ import hoistStatics from 'hoist-non-react-statics' import invariant from 'invariant' -import { Component, createElement } from 'react' +import React, { Component, createElement } from 'react' import Subscription from '../utils/Subscription' +import {ReactReduxContext} from "./context"; import { storeShape, subscriptionShape } from '../utils/PropTypes' let hotReloadingVersion = 0 const dummyState = {} function noop() {} -function makeSelectorStateful(sourceSelector, store) { +function makeSelectorStateful(sourceSelector) { // wrap the selector in an object that tracks its results between runs. const selector = { - run: function runComponentSelector(props) { + run: function runComponentSelector(props, storeState) { try { - const nextProps = sourceSelector(store.getState(), props) + const nextProps = sourceSelector(storeState, props) if (nextProps !== selector.props || selector.error) { selector.shouldComponentUpdate = true selector.props = nextProps @@ -78,6 +79,7 @@ export default function connectAdvanced( const subscriptionKey = storeKey + 'Subscription' const version = hotReloadingVersion++ + /* const contextTypes = { [storeKey]: storeShape, [subscriptionKey]: subscriptionShape, @@ -85,10 +87,11 @@ export default function connectAdvanced( const childContextTypes = { [subscriptionKey]: subscriptionShape, } + */ return function wrapWithConnect(WrappedComponent) { invariant( - typeof WrappedComponent == 'function', + typeof WrappedComponent === 'function', `You must pass a component to the function returned by ` + `${methodName}. Instead received ${JSON.stringify(WrappedComponent)}` ) @@ -117,22 +120,30 @@ export default function connectAdvanced( super(props, context) this.version = version - this.state = {} + //this.state = {} this.renderCount = 0 - this.store = props[storeKey] || context[storeKey] - this.propsMode = Boolean(props[storeKey]) + //this.store = props[storeKey] || context[storeKey] + //this.propsMode = Boolean(props[storeKey]) + + this.storeState = null; + + this.setWrappedInstance = this.setWrappedInstance.bind(this) + this.renderChild = this.renderChild.bind(this); + /* invariant(this.store, `Could not find "${storeKey}" in either the context or props of ` + `"${displayName}". Either wrap the root component in a , ` + `or explicitly pass "${storeKey}" as a prop to "${displayName}".` ) + */ - this.initSelector() - this.initSubscription() + //this.initSelector() + //this.initSubscription() } + /* getChildContext() { // If this component received store from props, its subscription should be transparent // to any descendants receiving store+subscription from context; it passes along @@ -141,6 +152,7 @@ export default function connectAdvanced( const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } + */ componentDidMount() { if (!shouldHandleStateChanges) return @@ -151,24 +163,28 @@ export default function connectAdvanced( // To handle the case where a child component may have triggered a state change by // dispatching an action in its componentWillMount, we have to re-run the select and maybe // re-render. - this.subscription.trySubscribe() - this.selector.run(this.props) + //this.subscription.trySubscribe() + this.selector.run(this.props, this.storeState); if (this.selector.shouldComponentUpdate) this.forceUpdate() } - componentWillReceiveProps(nextProps) { - this.selector.run(nextProps) + + UNSAFE_componentWillReceiveProps(nextProps) { + this.selector.run(nextProps, this.storeState); } + + shouldComponentUpdate() { return this.selector.shouldComponentUpdate } + componentWillUnmount() { - if (this.subscription) this.subscription.tryUnsubscribe() - this.subscription = null + //if (this.subscription) this.subscription.tryUnsubscribe() + //this.subscription = null this.notifyNestedSubs = noop - this.store = null + //this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false } @@ -185,13 +201,22 @@ export default function connectAdvanced( this.wrappedInstance = ref } + /* initSelector() { const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector, this.store) this.selector.run(this.props) } + */ + + initSelector(dispatch, storeState) { + const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions) + this.selector = makeSelectorStateful(sourceSelector) + this.selector.run(this.props, storeState); + } initSubscription() { + /* if (!shouldHandleStateChanges) return // parentSub's source should match where store came from: props vs. context. A component @@ -206,6 +231,7 @@ export default function connectAdvanced( // listeners logic is changed to not call listeners that have been unsubscribed in the // middle of the notification loop. this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) + */ } onStateChange() { @@ -229,12 +255,17 @@ export default function connectAdvanced( this.notifyNestedSubs() } + /* isSubscribed() { return Boolean(this.subscription) && this.subscription.isSubscribed() } + */ addExtraProps(props) { - if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props + //if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props + if (!withRef && !renderCountProp) return props; + + // make a shallow copy so that fields added don't leak to the original selector. // this is especially important for 'ref' since that's a reference back to the component // instance. a singleton memoized selector would then be holding a reference to the @@ -242,10 +273,51 @@ export default function connectAdvanced( const withExtras = { ...props } if (withRef) withExtras.ref = this.setWrappedInstance if (renderCountProp) withExtras[renderCountProp] = this.renderCount++ - if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription + //if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription return withExtras } + renderChild(providerValue) { + const {storeState, dispatch} = providerValue; + + this.storeState = storeState; + + //console.log(`Running renderChild (${displayName})`, storeState, this.props); + + if(this.selector) { + this.selector.run(this.props, storeState); + } + else { + this.initSelector(dispatch, storeState); + } + + + + if (this.selector.error) { + throw this.selector.error + } + else if(this.selector.shouldComponentUpdate) { + console.log(`Re-rendering component (${displayName})`, this.selector.props); + this.selector.shouldComponentUpdate = false; + this.renderedElement = createElement(WrappedComponent, this.addExtraProps(this.selector.props)); + } + else { + //console.log(`Returning existing render result (${displayName})`, this.props) + } + + return this.renderedElement; + } + + render() { + return ( + + {this.renderChild} + + ) + } + + /* + render() { const selector = this.selector selector.shouldComponentUpdate = false @@ -256,14 +328,16 @@ export default function connectAdvanced( return createElement(WrappedComponent, this.addExtraProps(selector.props)) } } + */ } Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName - Connect.childContextTypes = childContextTypes - Connect.contextTypes = contextTypes - Connect.propTypes = contextTypes + //Connect.childContextTypes = childContextTypes + //Connect.contextTypes = contextTypes + //Connect.propTypes = contextTypes + /* if (process.env.NODE_ENV !== 'production') { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! @@ -290,6 +364,7 @@ export default function connectAdvanced( } } } + */ return hoistStatics(Connect, WrappedComponent) } diff --git a/src/components/context.js b/src/components/context.js new file mode 100644 index 000000000..ba029da20 --- /dev/null +++ b/src/components/context.js @@ -0,0 +1,3 @@ +import React from "react"; + +export const ReactReduxContext = React.createContext(null); \ No newline at end of file