前言

react-redux与redux密切相关,建议看看前一篇redux源码解析,上一篇中还有一个redux api bindActionCreators没说,也在这里讲解;当然它的使用并不限于这里。你可以直接使用redux而不使用react-redux,因为redux作为flux思想的一种实现,你还可以和Angular,Ember等其他框架使用。如果要使用就要先理解下容器组件(container components)和展示组件(presentational components)的区别。react-redux只export了两个方法Provider和connect

官方文档在这里: http://redux.js.org/docs/basics/UsageWithReact.html

provider

provider源码很简单。基本就是直接将props中的store放到context中传递给子组件,所以在子组件中可以在this.context中获取到store。这是context的基本用法。贴一下provider的代码。不解释了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export default class Provider extends Component {
getChildContext() { //https://facebook.github.io/react/docs/context.html context provider
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store //Provider接受了store props
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired
}
Provider.childContextTypes = { //将store暴露给子组件
store: storeShape.isRequired
}

connect

connect方法的意义在于从展示组件生成容器组件。展示组件只有dom和style,不带有数据和逻辑,它的数据和逻辑需要从容器组件中获取,这样使得展示组件更加可复用。容器组件就是一个React组件,它使用store.subscribe()读取redux state tree中的一部分作为props传递给展示组件render,一旦它subscribe的这一部分state改变也同时会引起props的改变重新render。官方推荐使用connect function,它做了很多防止不必要re-render的优化。
先看整体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
const shouldSubscribe = Boolean(mapStateToProps)
const mapState = mapStateToProps || defaultMapStateToProps
let mapDispatch
if (typeof mapDispatchToProps === 'function') {
mapDispatch = mapDispatchToProps
} else if (!mapDispatchToProps) {
mapDispatch = defaultMapDispatchToProps
} else {
mapDispatch = wrapActionCreators(mapDispatchToProps)
}
const finalMergeProps = mergeProps || defaultMergeProps
const { pure = true, withRef = false } = options
const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps
// Helps track hot reloading.
const version = nextVersion++
return function wrapWithConnect(WrappedComponent) {
//************
}
}

入参有:
1.mapStateToProps: type是一个函数,参数为state和nextProp,返回值就是传入到Index组件中的props集合,既然有state,也就符合它的功能,将state tree的一部分映射到组件的props;

2.mapDispatchToProps: type可以是一个对象或者函数,以函数为例,它的作用是将action映射到容器组件props中,这样在组建内部就可以dispatch action来改变state tree,所以对state的读写相当于都有了;从上面代码也可以看出如果mapDispatchToProps不是function是对象。会运行redux的bindActionCreators方法:

1
2
3
export default function wrapActionCreators(actionCreators) {
return dispatch => bindActionCreators(actionCreators, dispatch)
}

而在bindActionCreators方法中,可以看到,如果actionCreators是对象,会使用Object.keys获取所有key。然后遍历每个属性调用bindActionCreator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

最后bindActionCreators返回值会是这种形式

1
2
3
4
5
//以下面indexGetBanner为例
{
indexGetBanner1: (...args) => dispatch(indexGetBanner(..arg)),
indexGetBanner2: (...args) => dispatch(indexGetBanner(..arg)),
}

而wrapActionCreators得返回值就是这样

1
2
3
4
5
6
(dispatch) => {
return {
indexGetBanner1: (...args) => dispatch(indexGetBanner(..arg)),
indexGetBanner2: (...args) => dispatch(indexGetBanner(..arg))
}
}

这种返回值与直接mapDispatchToProps传递方法的形式一模一样,所以bindActionCreators仅仅是对对象格式的参数进行了转化

3.mergeProps: 后续看作用;

具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Index.propTypes = {
needMall: PropTypes.bool.isRequired,
bannerList: PropTypes.array.isRequired,
indexGetBanner: PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
needMall: state.Index.needMall,
bannerList: state.Index.bannerList
}
}
const mapDispatchToProps = (dispatch) => { //为方法
return{
indexGetBanner: () => dispatch(indexGetBanner())
}
}
const mapDispatchToProps = { //为对象
indexGetBanner: () => indexGetBanner()
}
export default connect(mapStateToProps, mapDispatchToProps)(Index)

最终还是wrapWithConnect(WrappedComponent)返回了容器组件。接下来看这里面。在这里面我们看到了react的很多生命周期回调,如componentDidMount,componentWillUnmount,componentWillReceiveProps。作用如下

1.componentDidMount: 调用trySubscribe,也就是store.subscribe注册handleChange监听,之所以store暴露出来的subscribe我们没有用到,在这里就明白了,因为connect帮我们做了这些。

2.componentWillUnmount: 调用tryUnsubscribe,清除handleChange监听回调。调用clearCache重置各种标志位

3.componentWillReceiveProps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const { pure = true, withRef = false } = options //pure标识是否开启优化
/*******/
componentWillReceiveProps(nextProps) {
if (!pure || !shallowEqual(nextProps, this.props)) {
this.haveOwnPropsChanged = true
}
}
/*******/
export default function shallowEqual(objA, objB) { //判断两个对象是否相等 === 对于对象的比较是直接比较引用的地址
if (objA === objB) {
return true
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) {
return false
}
// Test for A's keys different from B.
const hasOwn = Object.prototype.hasOwnProperty
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false
}
}
return true
}

在handleChange中setState,引起render方法的调用,这两个方法中使用了大量标识变量来减少re-render。计算this.stateProps, this.dispatchProps; 在updateMergedPropsIfNeeded中对修改过的props进行merge。最终
createElement(WrappedComponent, {
…this.mergedProps,
ref: ‘wrappedInstance’
})
基于展示组件创建了一个renderedElement并返回。

最终返回的组件是这样的hoistStatics(Connect, WrappedComponent)是将WrappedComponent中的元素拷贝到Connect