react-redux源码解读

react-redux源码解读

上一篇翻了一下redux的源码 这一篇就研究下react-redux吧
原理大家都知道 就是利用react的Context Api
那我们看看它是怎么在react应用中施展魔法的吧~~

看代码 先看index.js

src/index.js

这个文件是主入口 暴露出 Provider/connectAdvanced/ReactReduxContext/connect四个核心方法

  1. 其中,Provider是个react组件 是存储store数的根节点
  2. ReactReduxContext是个react context,所有子组件的都要从这个context获取store的数据
  3. connect 是关联组件和store的方法
  4. connectAdvanced 是一个高阶组件 负责将connect的参数进行封装添加到子组件的props上
harmony
1
2
3
4
5
6
import Provider from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import { ReactReduxContext } from './components/Context'
import connect from './connect/connect'

export { Provider, connectAdvanced, ReactReduxContext, connect }

看到这几个方法就能猜到react-redux到底做了什么?难点在哪里?怎么避免不必要的渲染?以及使用高阶函数时对于ref的转发?

我们不妨先看Provider这个组件吧 毕竟他也是react使用redux的入口 也是关联store的第一环

src/components/Provider.js

Provider是一个react组件 接收三个props

  1. store
  2. context
  3. children
    由此我们猜测 react-redux内部肯定会使用相当多的闭包进行缓存以避免不必要的渲染
    结合connect的使用 推断会有根据storeState和ownProps进行判断是否进行渲染的逻辑 也理应会有
harmony
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Provider extends Component {
/*
* 将store和store当前的state保存在组件的state上
*/
constructor(props) {
super(props)

const { store } = props

this.state = {
storeState: store.getState(),
store
}
}

// 组件加载完执行subscribe
componentDidMount() {
this._isMounted = true
this.subscribe()
}

// 组件卸载之前
componentWillUnmount() {
// 取消监听
if (this.unsubscribe) this.unsubscribe()

this._isMounted = false
}

// 组件更新结束 如果store的引用变了 如果已经有监听 取消监听 重新监听
// 引用变了是指 执行reducer的过程中 redux/combineReducers里的hasChanged相对之前发生了变化
componentDidUpdate(prevProps) {
if (this.props.store !== prevProps.store) {
if (this.unsubscribe) this.unsubscribe()

this.subscribe()
}
}

subscribe() {
const { store } = this.props

// 对store添加监听
this.unsubscribe = store.subscribe(() => {
// 更新后的store树
const newStoreState = store.getState()

// 如果组件在卸载前 return
if (!this._isMounted) {
return
}

// 比较新旧store 减少不必要的更新
this.setState(providerState => {
// If the value is the same, skip the unnecessary state update.
if (providerState.storeState === newStoreState) {
return null
}

return { storeState: newStoreState }
})
})

// dispatch动作可能在render和componentDidUpdate之间进行 手动同步store
// Actions might have been dispatched between render and mount - handle those
const postMountStoreState = store.getState()
if (postMountStoreState !== this.state.storeState) {
this.setState({ storeState: postMountStoreState })
}
}

render() {
const Context = this.props.context || ReactReduxContext

return (
// 使用Context包裹children
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}

src/connect/connect.js

harmony
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
64
65
66
67
68
69
70
71
72
73
74
75
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
// 这个connect方法正是我们用到的connect(mapStateToProps, mapDispatchToProps)(WrapperComponent)的真面目
// mapStateToProps、mapDispatchToProps等参数我就不一一介绍了 请移步官网
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
// 是否启用PureComponent模式
pure = true,
// 在调用selectorFactory的时候 为了使用缓存需要调用的比对方法
areStatesEqual = strictEqual,
// 同上
areOwnPropsEqual = shallowEqual,
// 同上
areStatePropsEqual = shallowEqual,
// 同上
areMergedPropsEqual = shallowEqual,
// 别的参数 getDisplayName/methodName/renderCountProp/shouldHandleStateChanges等...
...extraOptions
} = {}
) {
/*
* wrapper的存在是为了让各种类型的返回值保持一致
* 如果mapStateToProps是function类型 return (dispatch, { displayName }) => proxy
* 如果不存在 return (dispatch, option) => constantSelector
* */
// 这三个情况类似 后面就看initMapStateToProps的执行过程就够了
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

// 这个暂时可以看作 return function(WrapperComponent) {}这样一个接收一个组件的方法
return connectHOC(selectorFactory, {
// used in error messages
methodName: 'connect',

// used to compute Connect's displayName from the wrapped component's displayName.
getDisplayName: name => `Connect(${name})`,

// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),

// passed through to selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,

// any extra options args can override defaults of connect or connectAdvanced
...extraOptions
})
}
}

// 使用默认参数返回一个connect高阶函数
export default createConnect()

这个createConnect方法接收5个参数

  1. connectHoc 默认值为一个高阶函数
  2. mapStateToPropsFactories 为用户传入的mapStateToProps值准备的类型映射 每个类型对应不同的执行方法
  3. mapDispatchToPropsFactories 同上
  4. mergePropsFactories 同上
  5. selectorFactory 这个是通过mapStateToProps和mapDispatchToProps获取最终stateProps, dispatchProps并将它们merge到组件props的过程

match方法
match方法会依次执行第二个参数的每个元素 入参为第一个参数 只要有返回值 就return 否则抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function 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
5
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)

match的第一个参数为用户传入的mapStateToProps 通常是这样的

1
2
3
const mapStateToProps = state => ({
username: state.profile.username,
})

第二个参数为mapStateToPropsFactories 默认值为defaultMapStateToPropsFactories 我们看看他是何方神圣

src/connect/mapStateToProps.js

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

export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}

export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]

从这个文件可以知道defaultMapStateToPropsFactories就是暴露出来的这个数组
上面我们提到过 这个东西是干嘛用的呢 这是个参数类型映射集合
从名字可以看出mapStateToProps可以是function 也可以为空

我们上面举了个function的🌰 先看是function的情况吧

1
2
3
4
5
function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}

这个方法判断如果mapStateToProps是function类型的就返回wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’)的执行结果
我们移步看看wrapMapToPropsFunc是何方神圣吧

src/connect/wrapMapToProps.js

1
2
3
4
5
export function wrapMapToPropsFunc(mapToProps, methodName) {
return function initProxySelector(dispatch, { displayName }) {
// 这里暂时先省略
}
}

第一次看这个代码有点懵逼,这是个啥,又没有什么上下文之类的 这个不用管
只需要知道返回的是一个(dispatch, options) => {} 这样的initProxySelector函数就行了
当mapStateToProps传空的时候也是类似这种情况 代码就不贴了 下面我们会回顾这个代码的

src/components/connectAdvanced.js

harmony
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
export default function connectAdvanced(
selectorFactory,
{
getDisplayName = name => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
forwardRef = false,
context = ReactReduxContext,
...connectOptions
} = {}
) {
invariant(
renderCountProp === undefined,
`renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
)

invariant(
!withRef,
'withRef is removed. To access the wrapped instance, use a ref on the connected component'
)

const customStoreWarningMessage =
'To use a custom Redux store for specific components, create a custom React context with ' +
"React.createContext(), and pass the context object to React Redux's Provider and specific components" +
' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
'You may also pass a {context : MyContext} option to connect'

invariant(
storeKey === 'store',
'storeKey has been removed and does not do anything. ' +
customStoreWarningMessage
)

const Context = context

return function wrapWithConnect(WrappedComponent) {
if (process.env.NODE_ENV !== 'production') {
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${stringifyComponent(
WrappedComponent
)}`
)
}

const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'

const displayName = getDisplayName(wrappedComponentName)

const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}

const { pure } = connectOptions

let OuterBaseComponent = Component
// 根据pure 选择使用Component还是PureComponent
if (pure) {
OuterBaseComponent = PureComponent
}

class Connect extends OuterBaseComponent {}

Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName

if (forwardRef) {
// 转发ref到Connect组件 将props转化为wrapperProps ref转化为forwardedRef
const forwarded = React.forwardRef(function forwardConnectRef(
props,
ref
) {
return <Connect wrapperProps={props} forwardedRef={ref} />
})

forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
// 合并static methods
return hoistStatics(forwarded, WrappedComponent)
}

return hoistStatics(Connect, WrappedComponent)
}
}

这段代码的作用主要是就是返回一个新的组件
重点看 Connect组件

harmony
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
64
65
66
67
68
69
70
class Connect extends OuterBaseComponent {
constructor(props) {
super(props)
invariant(
forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey],
'Passing redux store in props has been removed and does not do anything. ' +
customStoreWarningMessage
)

this.selectDerivedProps = makeDerivedPropsSelector()
this.selectChildElement = makeChildElementSelector()
this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(
this
)
}

// Context.Consumer的children
indirectRenderWrappedComponent(value) {
// calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
return this.renderWrappedComponent(value)
}

renderWrappedComponent(value) {
invariant(
value,
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
const { storeState, store } = value

let wrapperProps = this.props
let forwardedRef

if (forwardRef) {
wrapperProps = this.props.wrapperProps
forwardedRef = this.props.forwardedRef
}

let derivedProps = this.selectDerivedProps(
storeState,
wrapperProps,
store,
selectorFactoryOptions
)

return this.selectChildElement(
WrappedComponent,
derivedProps,
forwardedRef
)
}

render() {
// 使用默认的context源还是 外部传入的context
const ContextToUse =
this.props.context &&
this.props.context.Consumer &&
isContextConsumer(<this.props.context.Consumer />)
? this.props.context
: Context

return (
<ContextToUse.Consumer>
{this.indirectRenderWrappedComponent}
</ContextToUse.Consumer>
)
}
}

先从构造函数看起

1
2
this.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
64
function 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
}
}
  1. 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
35
export 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的源码 后续还会有补充…