react-redux 源码解析(1) — Provider
react-redux 源码解析(2) — Connect(上)
react-redux 源码解析(3) — Connect(下)
—————————————分割线—————————————–
上文我们分析了mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactor,下面我们要正式介绍connect的核心方法,connectAdvanced。此方法代码比较多,咱们分步介绍。
// 整体代码
export default function connectAdvanced(
selectorFactory,
// options object:
{
getDisplayName = (name) => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
forwardRef = false,
context = ReactReduxContext,
// additional options are passed through to the selectorFactory
...connectOptions
} = {}
) {
return function wrapWithConnect(WrappedComponent) {
....
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
if (forwardRef) {
const forwarded = React.forwardRef(function forwardConnectRef(
props,
ref
) {
return <Connect {...props} reactReduxForwardedRef={ref} />
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)
})
})
复制代码
函数接受两个参数:selectorFactory与connectOptions(可选),返回一个高阶组件wrapWithConnect(以属性代理方式实现),高阶组件中创建了Connect, 最后返回了hoistStatics(解决高阶组件静态方法丢失问题,自动拷贝所有非React的静态方法)。这里还有个forwardRef ref透传,不了解的可以了解下react.forwardRef.整体代码还算简单,没啥难以理解的。那么看来关键还是在 ConnectFunction中了。我没来看下代码:
1. //
2. function createChildSelector(store) {
3. return selectorFactory(store.dispatch, selectorFactoryOptions)
4. }
5.
6. //pure 模式使用 usememo, 当props变动的时候这个Component会帮助我们自动做一个props的浅比较,如果浅比较相等则不会update组件,如果不相等才会update组件。
7. const usePureOnlyMemo = pure ? useMemo : (callback) => callback()
8.
9. //
10. function ConnectFunction(props) {
11. const [
12. propsContext,
13. reactReduxForwardedRef,
14. wrapperProps,
15. ] = useMemo(() => {
16.
17. const { reactReduxForwardedRef, ...wrapperProps } = props
18. return [props.context, reactReduxForwardedRef, wrapperProps]
19. }, [props])
20.
21. const ContextToUse = useMemo(() => {
22. return propsContext &&
23. propsContext.Consumer &&
24. isContextConsumer(<propsContext.Consumer />)
25. ? propsContext
26. : Context
27. }, [propsContext, Context])
28.
29. const contextValue = useContext(ContextToUse)
30.
31. const didStoreComeFromProps =
32. Boolean(props.store) &&
33. Boolean(props.store.getState) &&
34. Boolean(props.store.dispatch)
35. const didStoreComeFromContext =
36. Boolean(contextValue) && Boolean(contextValue.store)
37.
38. if (
39. process.env.NODE_ENV !== 'production' &&
40. !didStoreComeFromProps &&
41. !didStoreComeFromContext
42. ) {
43. ···
44. }
45.
46. const store = didStoreComeFromProps ? props.store : contextValue.store
47.
48. const childPropsSelector = useMemo(() => {
49.
50. return createChildSelector(store)
51. }, [store])
52.
53. const [subscription, notifyNestedSubs] = useMemo(() => {
54. if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
55.
56. const subscription = new Subscription(
57. store,
58. didStoreComeFromProps ? null : contextValue.subscription
59. )
60.
61. const notifyNestedSubs = subscription.notifyNestedSubs.bind(
62. subscription
63. )
64.
65. return [subscription, notifyNestedSubs]
66. }, [store, didStoreComeFromProps, contextValue])
67.
68.
69. const overriddenContextValue = useMemo(() => {
70. if (didStoreComeFromProps) {
71.
72. return contextValue
73. }
74.
75.
76. return {
77. ...contextValue,
78. subscription,
79. }
80. }, [didStoreComeFromProps, contextValue, subscription])
81.
82.
83. const [
84. [previousStateUpdateResult],
85. forceComponentUpdateDispatch,
86. ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
87.
88. if (previousStateUpdateResult && previousStateUpdateResult.error) {
89. throw previousStateUpdateResult.error
90. }
91.
92.
93. const lastChildProps = useRef() //最后的childProps(传递给WrappedComponent的props,这里记录的是上一次的props)
94. const lastWrapperProps = useRef(wrapperProps) // 父元素传递进来的props
95. const childPropsFromStoreUpdate = useRef() // store更新计算出来的childProps
96. const renderIsScheduled = useRef(false) // 是否在render中
97.
98. const actualChildProps = usePureOnlyMemo(() => {
99.
100. if (
101. childPropsFromStoreUpdate.current &&
102. wrapperProps === lastWrapperProps.current
103. ) {
104. return childPropsFromStoreUpdate.current
105. }
106.
107. return childPropsSelector(store.getState(), wrapperProps)
108. }, [store, previousStateUpdateResult, wrapperProps])
109.
110. useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
111. lastWrapperProps,
112. lastChildProps,
113. renderIsScheduled,
114. wrapperProps,
115. actualChildProps,
116. childPropsFromStoreUpdate,
117. notifyNestedSubs,
118. ])
119.
120. useIsomorphicLayoutEffectWithArgs(
121. subscribeUpdates,
122. [
123. shouldHandleStateChanges,
124. store,
125. subscription,
126. childPropsSelector,
127. lastWrapperProps,
128. lastChildProps,
129. renderIsScheduled,
130. childPropsFromStoreUpdate,
131. notifyNestedSubs,
132. forceComponentUpdateDispatch,
133. ],
134. [store, subscription, childPropsSelector]
135. )
136.
137. const renderedWrappedComponent = useMemo(
138. () => (
139. <WrappedComponent
140. {...actualChildProps}
141. ref={reactReduxForwardedRef}
142. />
143. ),
144. [reactReduxForwardedRef, WrappedComponent, actualChildProps]
145. )
146.
147. const renderedChild = useMemo(() => {
148. if (shouldHandleStateChanges) {
149.
150. return (
151. <ContextToUse.Provider value={overriddenContextValue}>
152. {renderedWrappedComponent}
153. </ContextToUse.Provider>
154. )
155. }
156.
157. return renderedWrappedComponent
158. }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
159.
160. return renderedChild
161. }
复制代码
首先看46行从contextValue中取出store。然后拿到childPropsSelector,会在98行函数中通过store.getState()和wrapperProps来计算出新的actualChildProps用于渲染子组件。
再往下看得到了subscription, notifyNestedSubs这两个,在前面文章提到过。这里的subscripion是一个事件对象,而notifyNestedSubs可以通知subscription的所有监听者,事件发生,执行回调。
再看到83行,这里是引起组件 ConnectFunction更新的关键,只有调用forceComponentUpdateDispatch函数,组件ConnectFunction才会更新。
storeStateUpdatesReducer直接返回一个数组,第一项就是dispatch的action的payload,payload属性即是forceComponentUpdateDispatch入参又是previousStateUpdateResult值。
98行开始计算真实的childProps也就是actualChildProps。首先看store和来自父元素的props是否没有更新过,如果是,则直接返回store更新计算出来的childProps,否则重新通过childPropsSelector计算childProps。
再往下看到captureWrapperProps函数,主要是缓存了wrapperProps值、actualChildProps值,是否在渲染重置为false。如果是store变更引起的更新,则通知subscription的订阅者需要拉取最新的state。
再往下又看到了subscribeUpdates。主要是订阅更新checkForUpdates,关联起subscription实例和checkForUpdates中的forceComponentUpdateDispatch。
我们再来看看checkForUpdates:
1. const checkForUpdates = () => {
2. if (didUnsubscribe) {
3. return
4. }
5.
6. const latestStoreState = store.getState()
7.
8. let newChildProps, error
9. try {
10.
11. newChildProps = childPropsSelector(
12. latestStoreState,
13. lastWrapperProps.current
14. )
15. } catch (e) {
16. error = e
17. lastThrownError = e
18. }
19.
20. if (!error) {
21. lastThrownError = null
22. }
23.
24. if (newChildProps === lastChildProps.current) {
25. if (!renderIsScheduled.current) {
26. notifyNestedSubs()
27. }
28. } else {
29.
30. lastChildProps.current = newChildProps
31. childPropsFromStoreUpdate.current = newChildProps
32. renderIsScheduled.current = true
33.
34. forceComponentUpdateDispatch({
35. type: 'STORE_UPDATED',
36. payload: {
37. error,
38. },
39. })
40. }
41. }
复制代码
首先看到11行,根据最新的state和wrapperProps来计算出newChildProps,如果上一次的childProps一样,那么当前组件不必更新。但是当组件不需要更新的时候则需要单独通知subscription的监听者,state是有更新的,因为每个组件监听的state是不一样的,虽然当前组件没有更新,但是别的组件会获取到新的state用以更新。
看到30行。更新当前组件需要调用方法forceComponentUpdateDispatch,并且设置缓存上lastChildProps.current = newChildProps和childPropsFromStoreUpdate.current = newChildProps,其中childPropsFromStoreUpdate.current会在 forceComponentUpdateDispatch 更新时候通知captureWrapperProps函数,通过notifyNestedSubs,通知subscription有state更新。
至此 connect大致介绍完毕,小伙伴们有兴趣可以参照文章自己再重新梳理一遍源码,说不定会有有趣的发现。