Created
December 27, 2017 14:08
-
-
Save cyan33/55f5ad417830d256e6bf4bab3b5dec83 to your computer and use it in GitHub Desktop.
connect v4.2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* author: Dan Abromov | |
*/ | |
const { Component, createElement } = require('react') | |
const storeShape = require('../utils/storeShape') | |
const shallowEqual = require('../utils/shallowEqual') | |
const wrapActionCreators = require('../utils/wrapActionCreators') | |
const isPlainObject = require('lodash/isPlainObject') | |
const hoistStatics = require('hoist-non-react-statics') | |
const invariant = require('invariant') | |
const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars | |
const defaultMapDispatchToProps = dispatch => ({ dispatch }) | |
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({ | |
...parentProps, | |
...stateProps, | |
...dispatchProps | |
}) | |
function getDisplayName(WrappedComponent) { | |
return WrappedComponent.displayName || WrappedComponent.name || 'Component' | |
} | |
// Helps track hot reloading. | |
let nextVersion = 0 | |
function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { | |
const shouldSubscribe = Boolean(mapStateToProps) | |
const finalMapStateToProps = mapStateToProps || defaultMapStateToProps | |
const finalMapDispatchToProps = isPlainObject(mapDispatchToProps) ? | |
wrapActionCreators(mapDispatchToProps) : | |
mapDispatchToProps || defaultMapDispatchToProps | |
const finalMergeProps = mergeProps || defaultMergeProps | |
const doStatePropsDependOnOwnProps = finalMapStateToProps.length !== 1 | |
const doDispatchPropsDependOnOwnProps = finalMapDispatchToProps.length !== 1 | |
const { pure = true, withRef = false } = options | |
// Helps track hot reloading. | |
const version = nextVersion++ | |
function computeStateProps(store, props) { | |
const state = store.getState() | |
const stateProps = doStatePropsDependOnOwnProps ? | |
finalMapStateToProps(state, props) : | |
finalMapStateToProps(state) | |
invariant( | |
isPlainObject(stateProps), | |
'`mapStateToProps` must return an object. Instead received %s.', | |
stateProps | |
) | |
return stateProps | |
} | |
function computeDispatchProps(store, props) { | |
const { dispatch } = store | |
const dispatchProps = doDispatchPropsDependOnOwnProps ? | |
finalMapDispatchToProps(dispatch, props) : | |
finalMapDispatchToProps(dispatch) | |
invariant( | |
isPlainObject(dispatchProps), | |
'`mapDispatchToProps` muxst return an object. Instead received %s.', | |
dispatchProps | |
) | |
return dispatchProps | |
} | |
function computeMergedProps(stateProps, dispatchProps, parentProps) { | |
const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps) | |
invariant( | |
isPlainObject(mergedProps), | |
'`mergeProps` must return an object. Instead received %s.', | |
mergedProps | |
) | |
return mergedProps | |
} | |
return function wrapWithConnect(WrappedComponent) { | |
class Connect extends Component { | |
shouldComponentUpdate() { | |
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged | |
} | |
constructor(props, context) { | |
super(props, context) | |
this.version = version | |
this.store = props.store || context.store | |
invariant(this.store, | |
`Could not find "store" in either the context or ` + | |
`props of "${this.constructor.displayName}". ` + | |
`Either wrap the root component in a <Provider>, ` + | |
`or explicitly pass "store" as a prop to "${this.constructor.displayName}".` | |
) | |
const storeState = this.store.getState() | |
this.state = { storeState } | |
this.clearCache() | |
} | |
updateStatePropsIfNeeded() { | |
const nextStateProps = computeStateProps(this.store, this.props) | |
if (this.stateProps && shallowEqual(nextStateProps, this.stateProps)) { | |
return false | |
} | |
this.stateProps = nextStateProps | |
return true | |
} | |
updateDispatchPropsIfNeeded() { | |
const nextDispatchProps = computeDispatchProps(this.store, this.props) | |
if (this.dispatchProps && shallowEqual(nextDispatchProps, this.dispatchProps)) { | |
return false | |
} | |
this.dispatchProps = nextDispatchProps | |
return true | |
} | |
updateMergedProps() { | |
this.mergedProps = computeMergedProps( | |
this.stateProps, | |
this.dispatchProps, | |
this.props | |
) | |
} | |
isSubscribed() { | |
return typeof this.unsubscribe === 'function' | |
} | |
trySubscribe() { | |
if (shouldSubscribe && !this.unsubscribe) { | |
this.unsubscribe = this.store.subscribe(::this.handleChange) | |
this.handleChange() | |
} | |
} | |
tryUnsubscribe() { | |
if (this.unsubscribe) { | |
this.unsubscribe() | |
this.unsubscribe = null | |
} | |
} | |
componentDidMount() { | |
this.trySubscribe() | |
} | |
componentWillReceiveProps(nextProps) { | |
if (!pure || !shallowEqual(nextProps, this.props)) { | |
this.haveOwnPropsChanged = true | |
} | |
} | |
componentWillUnmount() { | |
this.tryUnsubscribe() | |
this.clearCache() | |
} | |
clearCache() { | |
this.dispatchProps = null | |
this.stateProps = null | |
this.mergedProps = null | |
this.haveOwnPropsChanged = true | |
this.hasStoreStateChanged = true | |
this.renderedElement = null | |
} | |
handleChange() { | |
if (!this.unsubscribe) { | |
return | |
} | |
const prevStoreState = this.state.storeState | |
const storeState = this.store.getState() | |
// why there the reference check? | |
// if some actions are not handled, the store state stays unchanged, | |
// which leaves prevStoreState === storeState | |
if (!pure || prevStoreState !== storeState) { | |
this.hasStoreStateChanged = true | |
this.setState({ storeState }) | |
} | |
} | |
getWrappedInstance() { | |
invariant(withRef, | |
`To access the wrapped instance, you need to specify ` + | |
`{ withRef: true } as the fourth argument of the connect() call.` | |
) | |
return this.refs.wrappedInstance | |
} | |
render() { | |
const { | |
haveOwnPropsChanged, | |
hasStoreStateChanged, | |
renderedElement | |
} = this | |
this.haveOwnPropsChanged = false | |
this.hasStoreStateChanged = false | |
let shouldUpdateStateProps = true | |
let shouldUpdateDispatchProps = true | |
if (pure && renderedElement) { | |
shouldUpdateStateProps = hasStoreStateChanged || ( | |
haveOwnPropsChanged && doStatePropsDependOnOwnProps | |
) | |
shouldUpdateDispatchProps = | |
haveOwnPropsChanged && doDispatchPropsDependOnOwnProps | |
} | |
let haveStatePropsChanged = false | |
let haveDispatchPropsChanged = false | |
if (shouldUpdateStateProps) { | |
haveStatePropsChanged = this.updateStatePropsIfNeeded() | |
} | |
if (shouldUpdateDispatchProps) { | |
haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded() | |
} | |
let haveMergedPropsChanged = true | |
if ( | |
haveStatePropsChanged || | |
haveDispatchPropsChanged || | |
haveOwnPropsChanged | |
) { | |
this.updateMergedProps() | |
} else { | |
haveMergedPropsChanged = false | |
} | |
if (!haveMergedPropsChanged && renderedElement) { | |
return renderedElement | |
} | |
if (withRef) { | |
this.renderedElement = createElement(WrappedComponent, { | |
...this.mergedProps, | |
ref: 'wrappedInstance' | |
}) | |
} else { | |
this.renderedElement = createElement(WrappedComponent, | |
this.mergedProps | |
) | |
} | |
return this.renderedElement | |
} | |
} | |
Connect.displayName = `Connect(${getDisplayName(WrappedComponent)})` | |
Connect.WrappedComponent = WrappedComponent | |
Connect.contextTypes = { | |
store: storeShape | |
} | |
Connect.propTypes = { | |
store: storeShape | |
} | |
if (process.env.NODE_ENV !== 'production') { | |
Connect.prototype.componentWillUpdate = function componentWillUpdate() { | |
if (this.version === version) { | |
return | |
} | |
// We are hot reloading! | |
this.version = version | |
this.trySubscribe() | |
this.clearCache() | |
} | |
} | |
return hoistStatics(Connect, WrappedComponent) | |
} | |
} | |
module.exports = connect |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment