Skip to content

Commit 5a44d43

Browse files
committed
Adds memoize option, defaulting to no memoization
1 parent 777652d commit 5a44d43

File tree

2 files changed

+72
-11
lines changed

2 files changed

+72
-11
lines changed

src/components/connect.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import wrapActionCreators from '../utils/wrapActionCreators'
66
import hoistStatics from 'hoist-non-react-statics'
77
import invariant from 'invariant'
88

9+
const defaultMemoizer = (fn) => fn
910
const defaultMapStateToProps = () => ({})
1011
const defaultMapDispatchToProps = dispatch => ({ dispatch })
1112
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
@@ -30,16 +31,16 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
3031
const finalMergeProps = mergeProps || defaultMergeProps
3132
const shouldUpdateStateProps = finalMapStateToProps.length > 1
3233
const shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1
33-
const { pure = true, withRef = false } = options
34+
const { pure = true, withRef = false, memoize = defaultMemoizer } = options
3435

3536
// Helps track hot reloading.
3637
const version = nextVersion++
3738

38-
function computeStateProps(store, props) {
39+
function computeStateProps(mapState, store, props) {
3940
const state = store.getState()
4041
const stateProps = shouldUpdateStateProps ?
41-
finalMapStateToProps(state, props) :
42-
finalMapStateToProps(state)
42+
mapState(state, props) :
43+
mapState(state)
4344

4445
invariant(
4546
isPlainObject(stateProps),
@@ -49,11 +50,11 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
4950
return stateProps
5051
}
5152

52-
function computeDispatchProps(store, props) {
53+
function computeDispatchProps(mapDispatch, store, props) {
5354
const { dispatch } = store
5455
const dispatchProps = shouldUpdateDispatchProps ?
55-
finalMapDispatchToProps(dispatch, props) :
56-
finalMapDispatchToProps(dispatch)
56+
mapDispatch(dispatch, props) :
57+
mapDispatch(dispatch)
5758

5859
invariant(
5960
isPlainObject(dispatchProps),
@@ -115,9 +116,13 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
115116
`Either wrap the root component in a <Provider>, ` +
116117
`or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
117118
)
119+
}
118120

119-
this.stateProps = computeStateProps(this.store, props)
120-
this.dispatchProps = computeDispatchProps(this.store, props)
121+
componentWillMount() {
122+
this.finalMapStateToProps = memoize(finalMapStateToProps)
123+
this.finalMapDispatchToProps = memoize(finalMapDispatchToProps)
124+
this.stateProps = computeStateProps(this.finalMapStateToProps, this.store, this.props)
125+
this.dispatchProps = computeDispatchProps(this.finalMapDispatchToProps, this.store, this.props)
121126
this.state = { storeState: null }
122127
this.updateState()
123128
}
@@ -131,7 +136,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
131136
}
132137

133138
updateStateProps(props = this.props) {
134-
const nextStateProps = computeStateProps(this.store, props)
139+
const nextStateProps = computeStateProps(this.finalMapStateToProps, this.store, props)
135140
if (shallowEqual(nextStateProps, this.stateProps)) {
136141
return false
137142
}
@@ -141,7 +146,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
141146
}
142147

143148
updateDispatchProps(props = this.props) {
144-
const nextDispatchProps = computeDispatchProps(this.store, props)
149+
const nextDispatchProps = computeDispatchProps(this.finalMapDispatchToProps, this.store, props)
145150
if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
146151
return false
147152
}
@@ -226,6 +231,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
226231
this.version = version
227232

228233
// Update the state and bindings.
234+
this.componentWillMount()
229235
this.trySubscribe()
230236
this.updateStateProps()
231237
this.updateDispatchProps()

test/components/connect.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'
44
import TestUtils from 'react-addons-test-utils'
55
import { createStore } from 'redux'
66
import { connect } from '../../src/index'
7+
import shallowEqual from '../../src/utils/shallowEqual'
78

89
describe('React', () => {
910
describe('connect', () => {
@@ -1369,5 +1370,59 @@ describe('React', () => {
13691370
// But render is not because it did not make any actual changes
13701371
expect(renderCalls).toBe(1)
13711372
})
1373+
1374+
it('defines finalMapStateToProps on the component', () => {
1375+
1376+
const store = createStore(() => ({
1377+
prefix: 'name: '
1378+
}))
1379+
let memoizeHits = 0
1380+
let memoizeMisses = 0
1381+
let renderCalls = 0
1382+
1383+
function memoize(func) {
1384+
let lastResult, lastArgs = lastResult = null
1385+
return (...args) => {
1386+
if (lastArgs && args.every((value, index) => shallowEqual(value, lastArgs[index]))) {
1387+
memoizeHits++
1388+
return lastResult
1389+
} else if (lastArgs) {
1390+
memoizeMisses++
1391+
}
1392+
lastArgs = args
1393+
lastResult = func(...args)
1394+
return lastResult
1395+
}
1396+
}
1397+
1398+
function selector(state, props) {
1399+
return { value: props.prefix + state.name }
1400+
}
1401+
1402+
@connect(selector, null, null, { memoize })
1403+
class Container extends Component {
1404+
componentDidMount() {
1405+
this.forceUpdate()
1406+
}
1407+
render() {
1408+
renderCalls++
1409+
return <div>{this.props.value}</div>
1410+
}
1411+
}
1412+
1413+
TestUtils.renderIntoDocument(
1414+
<ProviderMock store={store}>
1415+
<div>
1416+
<Container name="one" />
1417+
<Container name="two" />
1418+
</div>
1419+
</ProviderMock>
1420+
)
1421+
1422+
expect(renderCalls).toEqual(4)
1423+
expect(memoizeHits).toEqual(2)
1424+
expect(memoizeMisses).toEqual(0)
1425+
})
1426+
13721427
})
13731428
})

0 commit comments

Comments
 (0)