Skip to content

Commit a4d2529

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

File tree

2 files changed

+81
-11
lines changed

2 files changed

+81
-11
lines changed

src/components/connect.js

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,22 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
3030
const finalMergeProps = mergeProps || defaultMergeProps
3131
const shouldUpdateStateProps = finalMapStateToProps.length > 1
3232
const shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1
33-
const { pure = true, withRef = false } = options
33+
const { pure = true, withRef = false, memoize = false } = options
34+
35+
invariant(
36+
(memoize === false || typeof memoize === 'function'),
37+
'`memoize` must be a function. Instead received %s.',
38+
memoize
39+
)
3440

3541
// Helps track hot reloading.
3642
const version = nextVersion++
3743

38-
function computeStateProps(store, props) {
44+
function computeStateProps(mapState, store, props) {
3945
const state = store.getState()
4046
const stateProps = shouldUpdateStateProps ?
41-
finalMapStateToProps(state, props) :
42-
finalMapStateToProps(state)
47+
mapState(state, props) :
48+
mapState(state)
4349

4450
invariant(
4551
isPlainObject(stateProps),
@@ -49,11 +55,11 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
4955
return stateProps
5056
}
5157

52-
function computeDispatchProps(store, props) {
58+
function computeDispatchProps(mapDispatch, store, props) {
5359
const { dispatch } = store
5460
const dispatchProps = shouldUpdateDispatchProps ?
55-
finalMapDispatchToProps(dispatch, props) :
56-
finalMapDispatchToProps(dispatch)
61+
mapDispatch(dispatch, props) :
62+
mapDispatch(dispatch)
5763

5864
invariant(
5965
isPlainObject(dispatchProps),
@@ -115,9 +121,17 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
115121
`Either wrap the root component in a <Provider>, ` +
116122
`or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
117123
)
124+
}
118125

119-
this.stateProps = computeStateProps(this.store, props)
120-
this.dispatchProps = computeDispatchProps(this.store, props)
126+
componentWillMount() {
127+
this.finalMapStateToProps = memoize ?
128+
memoize(finalMapStateToProps) :
129+
finalMapStateToProps
130+
this.finalMapDispatchToProps = memoize ?
131+
memoize(finalMapDispatchToProps) :
132+
finalMapDispatchToProps
133+
this.stateProps = computeStateProps(this.finalMapStateToProps, this.store, this.props)
134+
this.dispatchProps = computeDispatchProps(this.finalMapDispatchToProps, this.store, this.props)
121135
this.state = { storeState: null }
122136
this.updateState()
123137
}
@@ -131,7 +145,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
131145
}
132146

133147
updateStateProps(props = this.props) {
134-
const nextStateProps = computeStateProps(this.store, props)
148+
const nextStateProps = computeStateProps(this.finalMapStateToProps, this.store, props)
135149
if (shallowEqual(nextStateProps, this.stateProps)) {
136150
return false
137151
}
@@ -141,7 +155,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
141155
}
142156

143157
updateDispatchProps(props = this.props) {
144-
const nextDispatchProps = computeDispatchProps(this.store, props)
158+
const nextDispatchProps = computeDispatchProps(this.finalMapDispatchToProps, this.store, props)
145159
if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
146160
return false
147161
}
@@ -226,6 +240,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
226240
this.version = version
227241

228242
// Update the state and bindings.
243+
this.componentWillMount()
229244
this.trySubscribe()
230245
this.updateStateProps()
231246
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)