-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New API for connectDecorator implementation #16
Changes from all commits
f6ef06e
063777a
ce8bf1e
a788376
b446894
df18c6d
74c3d9d
44287dc
80ebc01
170b1b7
105a2aa
9256e9d
c54a8b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,15 @@ | ||
import createProvider from './createProvider'; | ||
import createProvideDecorator from './createProvideDecorator'; | ||
|
||
import createConnector from './createConnector'; | ||
import createConnectDecorator from './createConnectDecorator'; | ||
|
||
export default function createAll(React) { | ||
// Wrapper components | ||
const Provider = createProvider(React); | ||
const Connector = createConnector(React); | ||
|
||
// Higher-order components (decorators) | ||
const provide = createProvideDecorator(React, Provider); | ||
const connect = createConnectDecorator(React, Connector); | ||
const connect = createConnectDecorator(React); | ||
|
||
return { Provider, Connector, provide, connect }; | ||
return { Provider, provide, connect }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,125 @@ | ||
import createStoreShape from '../utils/createStoreShape'; | ||
import getDisplayName from '../utils/getDisplayName'; | ||
import shallowEqualScalar from '../utils/shallowEqualScalar'; | ||
import shallowEqual from '../utils/shallowEqual'; | ||
import isPlainObject from '../utils/isPlainObject'; | ||
import wrapActionCreators from '../utils/wrapActionCreators'; | ||
import invariant from 'invariant'; | ||
|
||
export default function createConnectDecorator(React, Connector) { | ||
const { Component } = React; | ||
const emptySelector = () => ({}); | ||
|
||
return function connect(select) { | ||
return DecoratedComponent => class ConnectorDecorator extends Component { | ||
static displayName = `Connector(${getDisplayName(DecoratedComponent)})`; | ||
const emptyBinder = () => ({}); | ||
|
||
const identityMerge = (slice, actionsCreators, props) => ({ ...props, ...slice, ...actionsCreators}); | ||
|
||
|
||
export default function createConnectDecorator(React) { | ||
const { Component, PropTypes } = React; | ||
const storeShape = createStoreShape(PropTypes); | ||
|
||
return function connect(select, dispatchBinder = emptyBinder, mergeHandler = identityMerge) { | ||
|
||
const subscribing = select ? true : false; | ||
const selectState = select || emptySelector; | ||
const bindDispatch = isPlainObject(dispatchBinder) ? wrapActionCreators(dispatchBinder) : dispatchBinder; | ||
const merge = mergeHandler; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the point of that one, it would be actually easier to keep it called mergeHandler to distinguish easily from the Component.merge There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. per lint rules I can't reassign the function arguments so it needs a different name if we want an opportunity to do anything with the argument (which with default args we aren't at the moment) then it will need a different name. I actually don't mind that these const values overload the naming of the local component methods since it implies a delegating type relationship (which is exactly how they are used). I'd prefer to leave these alone but will take additional input |
||
|
||
return DecoratedComponent => class ConnectDecorator extends Component { | ||
static displayName = `ConnectDecorator(${getDisplayName(DecoratedComponent)})`; | ||
static DecoratedComponent = DecoratedComponent; | ||
|
||
shouldComponentUpdate(nextProps) { | ||
return !shallowEqualScalar(this.props, nextProps); | ||
static contextTypes = { | ||
store: storeShape.isRequired | ||
}; | ||
|
||
shouldComponentUpdate(nextProps, nextState) { | ||
return (this.subscribed && !this.isSliceEqual(this.state.slice, nextState.slice)) || | ||
!shallowEqualScalar(this.props, nextProps); | ||
} | ||
|
||
render() { | ||
return ( | ||
<Connector select={state => select(state, this.props)}> | ||
{stuff => <DecoratedComponent {...stuff} {...this.props} />} | ||
</Connector> | ||
isSliceEqual(slice, nextSlice) { | ||
const isRefEqual = slice === nextSlice; | ||
if (isRefEqual) { | ||
return true; | ||
} else if (typeof slice !== 'object' || typeof nextSlice !== 'object') { | ||
return isRefEqual; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (isRefEqual || (typeof slice !== 'object' || typeof nextSlice !== 'object') {
return isRefEqual;
} would be cleaner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was lifted straight from Connector @gaearon is this a micro optimization already? |
||
return shallowEqual(slice, nextSlice); | ||
} | ||
|
||
constructor(props, context) { | ||
super(props, context); | ||
this.state = { | ||
...this.selectState(props, context), | ||
...this.bindDispatch(context) | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
if (subscribing) { | ||
this.subscribed = true; | ||
this.unsubscribe = this.context.store.subscribe(::this.handleChange); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you planning to stick to ES6 on this repo too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's no big deal here because it's not a public facing example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand the original comment? I believe the package builds to es5 for consumption by default. Are you referring to not using es7 features or something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're removing ES7 features from Redux examples just so people can work with something stable and don't assume Redux requires ES7. But using it in source is fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh gotcha, object spread and all that. |
||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
if (subscribing) { | ||
this.unsubscribe(); | ||
} | ||
} | ||
|
||
handleChange(props = this.props) { | ||
const nextState = this.selectState(props, this.context); | ||
if (!this.isSliceEqual(this.state.slice, nextState.slice)) { | ||
this.setState(nextState); | ||
} | ||
} | ||
|
||
selectState(props = this.props, context = this.context) { | ||
const state = context.store.getState(); | ||
const slice = selectState(state); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have found use for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gaearon in the other thread you said:
I haven't read it all out, but I think it's just not quite as efficient. Specifically, we could avoid a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there something preventing you from using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the merge argument works. I didn't realize that we could avoid running That said, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a common thing, but then it's weird you can't do the same for actions. And you can't because that would be a perf problem (functions being bound every time). At least |
||
|
||
invariant( | ||
isPlainObject(slice), | ||
'The return value of `select` prop must be an object. Instead received %s.', | ||
slice | ||
); | ||
|
||
return { slice }; | ||
} | ||
|
||
bindDispatch(context = this.context) { | ||
const { dispatch } = context.store; | ||
const actionCreators = bindDispatch(dispatch); | ||
|
||
invariant( | ||
isPlainObject(actionCreators), | ||
'The return value of `bindDispatch` prop must be an object. Instead received %s.', | ||
actionCreators | ||
); | ||
|
||
return { actionCreators }; | ||
} | ||
|
||
merge(props = this.props, state = this.state) { | ||
const { slice, actionCreators } = state; | ||
const merged = merge(slice, actionCreators, props); | ||
|
||
invariant( | ||
isPlainObject(merged), | ||
'The return value of `merge` prop must be an object. Instead received %s.', | ||
merged | ||
); | ||
|
||
return merged; | ||
} | ||
|
||
getUnderlyingRef() { | ||
return this.underlyingRef; | ||
} | ||
|
||
render() { | ||
return <DecoratedComponent ref={component => (this.underlyingRef = component)} {...this.merge()} />; | ||
} | ||
}; | ||
}; | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import React from 'react'; | ||
import createAll from './components/createAll'; | ||
|
||
export const { Provider, Connector, provide, connect } = createAll(React); | ||
export const { Provider, Connector, provide, connect, connectDeprecated } = createAll(React); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gaearon should it still export Connector and connectDeprecated rather than removing it completely ? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import React from 'react-native'; | ||
import createAll from './components/createAll'; | ||
|
||
export const { Provider, Connector, provide, connect } = createAll(React); | ||
export const { Provider, Connector, provide, connect, connectDeprecated } = createAll(React); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { bindActionCreators } from 'redux'; | ||
|
||
export default function wrapActionCreators(actionCreators) { | ||
return dispatch => bindActionCreators(actionCreators, dispatch); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we rename select to selector as mentioned in #15 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in favor of arguments being named consistently as nouns or verbs. so select -> selector or change
selector <-> select
dispatchBinder <-> dispatchBind
mergeHandler <-> merge