react-redux中connect第二个参数为对象时源码分析

“ 记着当初才来公司的时候上手项目时,对redux的插件使用多少有些困惑,今天来讲解一下react-redux中connect方法第二个参数为对象时的封装。”

1. connect

首先找到connect的封装,在connect.js文件中,先简化一下它的代码封装,得到如下所示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
}) {
return function connect({
mapStateToProps,
mapDispatchToProps,
// ...
}) {
// ...
return connectHOC(selectorFactory, {
// ...
})
}
}

export default createConnect()

有没有瞬间感觉过程清晰了一些呢?其实在我们引用connect时就是引入了connect函数,而当我们调用connect的时候其实它回返了封装之后的组件,用专业术语说的话就是高阶组件,这个就是connect的一个特点。
如果你对connect整体感兴趣,不妨来看看它的源码,在这里我就不一一讲解了。接下来我会来分析connect中第二个参数mapDispatchToProps为对象时的具体流程。

2. mapDispatchToProps为对象时的执行过程

首先来看下面代码,可能你在日常开发中也会遇到或者写过的,但是你有没有想过它为什么会这样来执行呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export function dispatchTest() {
return ()=>{
// todo ...
console.log(1111);
};
}

// 组件中使用
import {connect} from 'react-redux';
// 这里是装饰器的写法,不懂的话可以自行网上搜索查看
@connect(state => ({}), { dispatchTest })
class Test extends Component {
componentDidMount () {
this.props.dispatchTest();
}
}

其实这个时候在componentDidMount生命周期中调用dispatchTest方法是能够打印出1111的,是不是此时你有点疑惑我不是只执行了一次,按理应该返回一个函数呀?不慌,听我慢慢给你道来。
首先去看connect方法中initMapDispatchToProps变量,它是match方法返回值,接着我们又去找match方法,然后我把重要的代码抽出来讲解,它具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
*@params mapDispatchToProps:connect方法中我们传递的第二个参数值
*/
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)

function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg)
if (result) return result
}

return (dispatch, options) => {
throw new Error(
`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
options.wrappedComponentName
}.`
)
}
}

上面的代码其实也是很简单的,mapDispatchToPropsFactories方法我们来解析一下,在这里我们只分析为对象时的函数源码,具体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant(dispatch =>
bindActionCreators(mapDispatchToProps, dispatch)
)
: undefined
}

export default [
whenMapDispatchToPropsIsObject
]

在这里我们重点看处理对象的那个函数,这个函数里首先先来看一下wrapMapToPropsConstant函数的封装,具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
export function wrapMapToPropsConstant(getConstant) {
return function initConstantSelector(dispatch, options) {
const constant = getConstant(dispatch, options)

function constantSelector() {
return constant
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}

接着再来看看bindActionCreators函数,它也是connect中很核心的一部分,它的代码封装如下所示:

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 function () {
return dispatch(actionCreator.apply(this, arguments));
};
}

// 首先actionCreators的值就是connect中我们传递的第二个参数,它是一个对象
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\"?");
}
// 在这里我们应该看下面的代码,为对象时,遍历它的key,如果为函数则返回bindActionCreator处理后的函数
var boundActionCreators = {};
for (var key in actionCreators) {
var actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}

然后对于上面的代码,然后将其进行一个整合之后可以得到变量initMapDispatchToProps的值为处理过后的函数,接着我们再去寻找它在哪里执行了,顺着代码找到connectAdvanced.js文件中的initSelector函数,接着在函数中找到selectorFactory函数执行,我们再去看看selectorFactory函数内容执行了什么代码,然后将其简化,只看重点代码,具体如下所示:

1
2
3
4
5
6
7
8
9
export default function finalPropsSelectorFactory(dispatch, {
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
...options
}) {
// 其实真正执行函数的是这行代码执行的
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
}

好了,今日的分享就到这里了,讲的不好还请见谅。如果你对connect内部有很浓烈的兴趣那么你可以去看看它内部其他的实现。