react-redux源码解读
上一篇翻了一下redux的源码 这一篇就研究下react-redux吧
原理大家都知道 就是利用react的Context Api
那我们看看它是怎么在react应用中施展魔法的吧~~
看代码 先看index.js
src/index.js
这个文件是主入口 暴露出 Provider/connectAdvanced/ReactReduxContext/connect四个核心方法
- 其中,Provider是个react组件 是存储store数的根节点
- ReactReduxContext是个react context,所有子组件的都要从这个context获取store的数据
- connect 是关联组件和store的方法
- connectAdvanced 是一个高阶组件 负责将connect的参数进行封装添加到子组件的props上
1 | import Provider from './components/Provider' |
看到这几个方法就能猜到react-redux到底做了什么?难点在哪里?怎么避免不必要的渲染?以及使用高阶函数时对于ref的转发?
我们不妨先看Provider这个组件吧 毕竟他也是react使用redux的入口 也是关联store的第一环
src/components/Provider.js
Provider是一个react组件 接收三个props
- store
- context
- children
由此我们猜测 react-redux内部肯定会使用相当多的闭包进行缓存以避免不必要的渲染
结合connect的使用 推断会有根据storeState和ownProps进行判断是否进行渲染的逻辑 也理应会有
1 | class Provider extends Component { |
src/connect/connect.js
1 | export function createConnect({ |
这个createConnect方法接收5个参数
- connectHoc 默认值为一个高阶函数
- mapStateToPropsFactories 为用户传入的mapStateToProps值准备的类型映射 每个类型对应不同的执行方法
- mapDispatchToPropsFactories 同上
- mergePropsFactories 同上
- selectorFactory 这个是通过mapStateToProps和mapDispatchToProps获取最终stateProps, dispatchProps并将它们merge到组件props的过程
match方法
match方法会依次执行第二个参数的每个元素 入参为第一个参数 只要有返回值 就return 否则抛出异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg)
// 此时result 的类型是 (dispatch, {}) => {}
if (result) return result
}
return (dispatch, options) => {
throw new Error(
`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
options.wrappedComponentName
}.`
)
}
}
我们看一下initMapStateToProps是怎么产生的1
2
3
4
5const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
match的第一个参数为用户传入的mapStateToProps 通常是这样的1
2
3const mapStateToProps = state => ({
username: state.profile.username,
})
第二个参数为mapStateToPropsFactories 默认值为defaultMapStateToPropsFactories 我们看看他是何方神圣
src/connect/mapStateToProps.js
1 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' |
从这个文件可以知道defaultMapStateToPropsFactories就是暴露出来的这个数组
上面我们提到过 这个东西是干嘛用的呢 这是个参数类型映射集合
从名字可以看出mapStateToProps可以是function 也可以为空
我们上面举了个function的🌰 先看是function的情况吧1
2
3
4
5function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
这个方法判断如果mapStateToProps是function类型的就返回wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’)的执行结果
我们移步看看wrapMapToPropsFunc是何方神圣吧
src/connect/wrapMapToProps.js
1 | export function wrapMapToPropsFunc(mapToProps, methodName) { |
第一次看这个代码有点懵逼,这是个啥,又没有什么上下文之类的 这个不用管
只需要知道返回的是一个(dispatch, options) => {} 这样的initProxySelector函数就行了
当mapStateToProps传空的时候也是类似这种情况 代码就不贴了 下面我们会回顾这个代码的
src/components/connectAdvanced.js
1 | export default function connectAdvanced( |
这段代码的作用主要是就是返回一个新的组件
重点看 Connect组件
1 | class Connect extends OuterBaseComponent { |
先从构造函数看起1
2this.selectDerivedProps = makeDerivedPropsSelector()
this.selectChildElement = makeChildElementSelector()
makeDerivedPropsSelector、makeChildElementSelector方法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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64function makeDerivedPropsSelector() {
let lastProps
let lastState
let lastDerivedProps
let lastStore
let lastSelectorFactoryOptions
let sourceSelector
return function selectDerivedProps(
state,
props,
store,
selectorFactoryOptions
) {
if (pure && lastProps === props && lastState === state) {
return lastDerivedProps
}
if (
store !== lastStore ||
lastSelectorFactoryOptions !== selectorFactoryOptions
) {
lastStore = store
lastSelectorFactoryOptions = selectorFactoryOptions
sourceSelector = selectorFactory(
store.dispatch,
selectorFactoryOptions
)
}
lastProps = props
lastState = state
const nextProps = sourceSelector(state, props)
lastDerivedProps = nextProps
return lastDerivedProps
}
}
function makeChildElementSelector() {
let lastChildProps, lastForwardRef, lastChildElement, lastComponent
return function selectChildElement(
WrappedComponent,
childProps,
forwardRef
) {
if (
childProps !== lastChildProps ||
forwardRef !== lastForwardRef ||
lastComponent !== WrappedComponent
) {
lastChildProps = childProps
lastForwardRef = forwardRef
lastComponent = WrappedComponent
lastChildElement = (
<WrappedComponent {...childProps} ref={forwardRef} />
)
}
return lastChildElement
}
}
- makeDerivedPropsSelector
这个方法这就要是给组件接收从store衍生的props做了一层缓存 细节可以自己去看 这边主要看一下用到的selectorFactory方法 这个方法是connectAdvanced的入参
回到src/connect/connect.js 这个selectorFactory有一个默认值为defaultSelectorFactory 我们看一下src/connect/selectorFactory.js
src/connect/selectorFactory.js
接收dispatch和options作为参数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
34
35export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
// initMapStateToProps、initMapDispatchToProps、initMergeProps就是在createConnect中 经由match计算的结果initProxySelector
// 所以我们在下面回顾一下这个方法
const mapStateToProps = initMapStateToProps(dispatch, options)
// 同上
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
)
}
// 根据pure选择不同的计算方法
// 如果pure为false 无脑根据mapStateToProps和mapDispatchToProps的值计算merge到组件props的结果
// 如果为true 会依据组件props是否改变或者stateStore是否变化相应的计算最终结果
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
initProxySelector方法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
34
35
36
37
38
39
function initProxySelector(dispatch, { displayName }) {
// 返回一个proxy方法 带有mapToProps属性
// 在高阶组收集statsToProps或者dispatchToPros的时候会触发这个函数
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
// allow detectFactoryAndVerify to get ownProps
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
// 将mapToProps赋值给proxy.mapToProps 这样下面的proxy(stateOrDispatch, ownProps) 实际上跑的就是传入的mapToProps了
proxy.mapToProps = mapToProps
// dependsOnOwnProps属性是为了后面避免重复计算的判断依据之一 也根据这个属性判断是否需要传入组件自身的props到mapStateToProps和mapDispatchToProps里
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
// 为了防止用户手动使用了缓存机制 判断是否存在闭包 做一层递归处理 知道得到最终mapStateToProps和mapDispatchToProps计算后的值
if (typeof props === 'function') {
// 此时props就是新的mapToProps方法
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, methodName)
return props
}
return proxy
}
回到Connect的构造函数 还有一个makeChildElementSelector方法
这个方法是的作用是对组件进行缓存 判断组件props ref和wrapperComponent返回新旧组件
以上就是简要的介绍了react-redux的源码 后续还会有补充…