react-redux 源码解析(3) — Connect(下)

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大致介绍完毕,小伙伴们有兴趣可以参照文章自己再重新梳理一遍源码,说不定会有有趣的发现。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享