Skip to content

Commit a648443

Browse files
committed
Merge pull request #25 from gaearon/new-api
New API
2 parents ae1aaef + 187532c commit a648443

16 files changed

+838
-584
lines changed

README.md

+246-113
Large diffs are not rendered by default.

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-redux",
3-
"version": "0.4.0",
4-
"description": "Redux bindings for React",
3+
"version": "0.5.0",
4+
"description": "React bindings for Redux",
55
"main": "./lib/index.js",
66
"scripts": {
77
"build:lib": "babel src --out-dir lib",
@@ -60,6 +60,6 @@
6060
"invariant": "^2.0.0"
6161
},
6262
"peerDependencies": {
63-
"redux": "^1.0.0 || 1.0.0-alpha || 1.0.0-rc"
63+
"redux": "^1.0.0 || 1.0.0-rc"
6464
}
6565
}

src/components/createAll.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import createProvider from './createProvider';
2-
3-
import createConnector from './createConnector';
4-
import createConnectDecorator from './createConnectDecorator';
2+
import createConnect from './createConnect';
53

64
export default function createAll(React) {
75
const Provider = createProvider(React);
8-
const connect = createConnectDecorator(React, createConnector(React));
6+
const connect = createConnect(React);
97

10-
// provider and Connector are deprecated and removed from public API
118
return { Provider, connect };
129
}

src/components/createConnect.js

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import createStoreShape from '../utils/createStoreShape';
2+
import shallowEqualScalar from '../utils/shallowEqualScalar';
3+
import shallowEqual from '../utils/shallowEqual';
4+
import isPlainObject from '../utils/isPlainObject';
5+
import wrapActionCreators from '../utils/wrapActionCreators';
6+
import invariant from 'invariant';
7+
8+
const defaultMapState = () => ({});
9+
const defaultMapDispatch = dispatch => ({ dispatch });
10+
const defaultMergeProps = (stateSlice, actionsCreators, props) => ({
11+
...props,
12+
...stateSlice,
13+
...actionsCreators
14+
});
15+
16+
function getDisplayName(Component) {
17+
return Component.displayName || Component.name || 'Component';
18+
}
19+
20+
export default function createConnect(React) {
21+
const { Component, PropTypes } = React;
22+
const storeShape = createStoreShape(PropTypes);
23+
24+
return function connect(
25+
mapState = defaultMapState,
26+
mapDispatchOrActionCreators = defaultMapDispatch,
27+
mergeProps = defaultMergeProps
28+
) {
29+
const shouldSubscribe = mapState !== defaultMapState;
30+
const mapDispatch = isPlainObject(mapDispatchOrActionCreators) ?
31+
wrapActionCreators(mapDispatchOrActionCreators) :
32+
mapDispatchOrActionCreators;
33+
34+
return DecoratedComponent => class ConnectDecorator extends Component {
35+
static displayName = `Connect(${getDisplayName(DecoratedComponent)})`;
36+
static DecoratedComponent = DecoratedComponent;
37+
38+
static contextTypes = {
39+
store: storeShape.isRequired
40+
};
41+
42+
shouldComponentUpdate(nextProps, nextState) {
43+
return (this.subscribed && !this.isSliceEqual(this.state.slice, nextState.slice)) ||
44+
!shallowEqualScalar(this.props, nextProps);
45+
}
46+
47+
isSliceEqual(slice, nextSlice) {
48+
const isRefEqual = slice === nextSlice;
49+
if (
50+
isRefEqual ||
51+
typeof slice !== 'object' ||
52+
typeof nextSlice !== 'object'
53+
) {
54+
return isRefEqual;
55+
}
56+
57+
return shallowEqual(slice, nextSlice);
58+
}
59+
60+
constructor(props, context) {
61+
super(props, context);
62+
this.setUnderlyingRef = ::this.setUnderlyingRef;
63+
this.state = {
64+
...this.mapState(props, context),
65+
...this.mapDispatch(context)
66+
};
67+
}
68+
69+
componentDidMount() {
70+
if (shouldSubscribe) {
71+
this.subscribed = true;
72+
this.unsubscribe = this.context.store.subscribe(::this.handleChange);
73+
}
74+
}
75+
76+
componentWillUnmount() {
77+
if (shouldSubscribe) {
78+
this.unsubscribe();
79+
}
80+
}
81+
82+
handleChange(props = this.props) {
83+
const nextState = this.mapState(props, this.context);
84+
if (!this.isSliceEqual(this.state.slice, nextState.slice)) {
85+
this.setState(nextState);
86+
}
87+
}
88+
89+
mapState(props = this.props, context = this.context) {
90+
const state = context.store.getState();
91+
const slice = mapState(state);
92+
93+
invariant(
94+
isPlainObject(slice),
95+
'`mapState` must return an object. Instead received %s.',
96+
slice
97+
);
98+
99+
return { slice };
100+
}
101+
102+
mapDispatch(context = this.context) {
103+
const { dispatch } = context.store;
104+
const actionCreators = mapDispatch(dispatch);
105+
106+
invariant(
107+
isPlainObject(actionCreators),
108+
'`mapDispatch` must return an object. Instead received %s.',
109+
actionCreators
110+
);
111+
112+
return { actionCreators };
113+
}
114+
115+
merge(props = this.props, state = this.state) {
116+
const { slice, actionCreators } = state;
117+
const merged = mergeProps(slice, actionCreators, props);
118+
119+
invariant(
120+
isPlainObject(merged),
121+
'`mergeProps` must return an object. Instead received %s.',
122+
merged
123+
);
124+
125+
return merged;
126+
}
127+
128+
getUnderlyingRef() {
129+
return this.underlyingRef;
130+
}
131+
132+
setUnderlyingRef(instance) {
133+
this.underlyingRef = instance;
134+
}
135+
136+
render() {
137+
return (
138+
<DecoratedComponent ref={this.setUnderlyingRef}
139+
{...this.merge()} />
140+
);
141+
}
142+
};
143+
};
144+
}

src/components/createConnectDecorator.js

-25
This file was deleted.

src/components/createConnector.js

-89
This file was deleted.

src/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react';
22
import createAll from './components/createAll';
33

4-
// provide and Connector are deprecated and removed from public API
54
export const { Provider, connect } = createAll(React);

src/native.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react-native';
22
import createAll from './components/createAll';
33

4-
// provide and Connector are deprecated and removed from public API
54
export const { Provider, connect } = createAll(React);

src/utils/getDisplayName.js

-3
This file was deleted.

src/utils/isPlainObject.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
const fnToString = (fn) => Function.prototype.toString.call(fn);
2+
3+
/**
4+
* @param {any} obj The object to inspect.
5+
* @returns {boolean} True if the argument appears to be a plain object.
6+
*/
17
export default function isPlainObject(obj) {
2-
return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false;
8+
if (!obj || typeof obj !== 'object') {
9+
return false;
10+
}
11+
12+
const proto = typeof obj.constructor === 'function' ?
13+
Object.getPrototypeOf(obj) :
14+
Object.prototype;
15+
16+
if (proto === null) {
17+
return true;
18+
}
19+
20+
const constructor = proto.constructor;
21+
22+
return typeof constructor === 'function'
23+
&& constructor instanceof constructor
24+
&& fnToString(constructor) === fnToString(Object);
325
}

src/utils/wrapActionCreators.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { bindActionCreators } from 'redux';
2+
3+
export default function wrapActionCreators(actionCreators) {
4+
return dispatch => bindActionCreators(actionCreators, dispatch);
5+
}

0 commit comments

Comments
 (0)