React-Redux のソースコード を読んでみる

`react-redux` を読んでみたので、そのメモ。
前回の `redux` のソースコードを読んだ[記事](https://qiita.com/YmBIgo/items/23f1cd2fb13465489d3f)の続きです。
コード読むのに慣れていないので、間違っている部分もあるかもしれません。あったらご指摘ください。
## 対象読者
`redux` を知っている人。
## react-redux の connect
connect 部分が気になったので、そこを読んでみました。
まず、connect は

```javascript
connect(mapStateToProps, mapDispatchToProps)(コンポーネント名)
```

のようにして書きますが、この `()()` の部分は[クロージャー](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures)と呼ばれる書き方になっていて、

```javascript
const appComponent = connect(mapStateToProps, mapDispatchToProps)
appComponent(コンポーネント名)
```

と同じ書き方になります。
その中身が、ここの[ソースコード](https://github.com/reduxjs/react-redux/blob/master/src/components/connect.tsx)に書いてあります。
ソースコードから、connect 部分だけを取り出すと、

```javascript:src/components/connect.tsx
・・・省略
function connect<
・・・省略 型
>(
  mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, State>,
  mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
  mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
・・・省略 
)
・・・省略
  const Context = context
  ・・・省略 (mapStateToProps, mapDispatchToProps の処理)
  const shouldHandleStateChanges = Boolean(mapStateToProps)

  const wrapWithConnect: AdvancedComponentDecorator<
    TOwnProps,
    WrappedComponentProps
  > = (WrappedComponent) => {
    ・・・省略
    function ConnectFunction<TOwnProps>(
      props: InternalConnectProps & TOwnProps
    ) {
      ・・・省略
      ・・・ここから 描画
      const renderedWrappedComponent = useMemo(() => {
        return (
          // @ts-ignore
          <WrappedComponent
            {...actualChildProps}
            ref={reactReduxForwardedRef}
          />
        )
      }, [reactReduxForwardedRef, WrappedComponent, actualChildProps])

      const renderedChild = useMemo(() => {
        if (shouldHandleStateChanges) {
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          )
        }

        return renderedWrappedComponent
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])

      return renderedChild
    }

    const _Connect = React.memo(ConnectFunction)
    ・・・省略 型
    const Connect = _Connect /* 省略 */
    Connect.WrappedComponent = WrappedComponent
    ・・・省略
    return hoistStatics(Connect, WrappedComponent)
  }
  return wrapWithConnect
}

export default connect as Connect
```

[hoistStatics](https://github.com/mridgway/hoist-non-react-statics/blob/v1.2.0/index.js) は [オブジェクトのコピーをしているくらい](https://qiita.com/juntaki/items/7df6a6cbca990281ced8#connectadvanced) で、`connect` の後半の () に wrapWithConnect が入るので、connect 全体では wrapWithConnect の引数の `<WrappedComponent>` か wrapWithConnect を children にした `<ContextToUse.Provider value=>` のみを返しているのが分かります。

では、

```javascript:src/components/connect.tsx　489〜506行目
  ・・・省略 (mapStateToProps, mapDispatchToProps の処理)
```

の部分を見てみましょう。
ここでは、`connect()()` の前半の () から mapStateToProps と mapDispatchToProps を取り出して、以下の関数に当てはめています。

```javascript:src/components/connect.tsx
  const initMapStateToProps = match(
    mapStateToProps,
    // @ts-ignore
    defaultMapStateToPropsFactories,
    'mapStateToProps'
  )!
  const initMapDispatchToProps = match(
    mapDispatchToProps,
    // @ts-ignore
    defaultMapDispatchToPropsFactories,
    'mapDispatchToProps'
  )!
  const initMergeProps = match(
    mergeProps,
    // @ts-ignore
    defaultMergePropsFactories,
    'mergeProps'
  )!
```

match は、

```javascript:src/components/connect.tsx
function match<T>(
  arg: unknown,
  factories: ((value: unknown) => T)[],
  name: string
): T {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

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

のように、`factories[i](arg)` の返り値を返しています。
では、factories の中身の `defaultMapStateToPropsFactories` と `defaultMapDispatchToPropsFactories ` はどうなっているんでしょうか？

`defaultMapStateToPropsFactories` は `src/connect/mapDispatchToProps.ts`に、　`defaultMapDispatchToPropsFactories ` は `src/connect/mapStateToProps.ts` に、export default で定義されているんですが、それぞれで共通して使われているのが `src/connect/wrapMapToProps.ts` にある関数 `wrapMapToPropsConstant` と `wrapMapToPropsFunc` です。

ここでは、`wrapMapToPropsFunc` を見てみましょう。

```javascript:src/connect/wrapMapToProps.ts
export function wrapMapToPropsFunc<P = AnyProps>(
  mapToProps: MapToProps,
  methodName: string
) {
  return function initProxySelector(
    dispatch: Dispatch,
    { displayName }: { displayName: string }
  ) {
    const proxy = function mapToPropsProxy(
      stateOrDispatch: StateOrDispatch,
      ownProps?: P
    ): MapToProps {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch, undefined)
    }
    // allow detectFactoryAndVerify to get ownProps
    proxy.dependsOnOwnProps = true

    proxy.mapToProps = function detectFactoryAndVerify(
      stateOrDispatch: StateOrDispatch,
      ownProps?: P
    ): MapToProps {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)

      ・・・省略

      return props
    }

    return proxy
  }
}
```

読みづらいですが、

```javascript:src/connect/wrapMapToProps.ts　92行目
      proxy.mapToProps = mapToProps
```

で、proxy の mapToProps に wrapMapToPropsFunc の引数の mapToProps を代入しているのが分かると思います。
この mapToProps の中に、下のような mapStateToProps と mapDispatchToProps が入ります。

```javascript
function mapStateToProps(state) {
  return {
    data: state.hoge
  };
}
```

```javascript
function mapDispatchToProps(dispatch) {
  return {
    hoge: () => dispatch({
      type: Actions.DISPATCH_HOGE,
      hoge: true,
    }),
  }
};
```

これで、

```javascript:src/components/connect.tsx
  const initMapStateToProps = match(・・・省略)
  const initMapDispatchToProps = match(・・・省略)
  const initMergeProps = match(・・・省略)
```

に無事に、返り値が入りました。
これらの `initMapStateToProps` と `initMapDispatchToProps` と `initMergeProps` は、`selectorFactoryOptions` に代入され、`ConnectFunction` 内の `childPropsSelector` で `src/connect/selectorFactory.ts` の `finalPropsSelectorFactory` に渡されます。

```javascript:src/components/connect.tsx
      const childPropsSelector = useMemo(() => {
        return defaultSelectorFactory(store.dispatch, selectorFactoryOptions)
      }, [store])
```

```javascript:src/connect/selectorFactory.ts
export default function finalPropsSelectorFactory
  (
  dispatch: Dispatch<Action>,
  {
    initMapStateToProps,
    initMapDispatchToProps,
    initMergeProps,
    ...options
  }
){
  const mapStateToProps = initMapStateToProps(dispatch, options)
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  const mergeProps = initMergeProps(dispatch, options)
  ・・・省略
  return pureFinalPropsSelectorFactory(mapStateToProps!, mapDispatchToProps, mergeProps, dispatch, options)
}
```

ここの返り値の pureFinalPropsSelectorFactory の中身は下のようになります。

```javascript:src/connect/selectorFactory.ts
export function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, {areStatesEqual, areOwnPropsEqual, areStatePropsEqual}) {
  let hasRunAtLeastOnce = false
  let state: State
  let ownProps: TOwnProps
  let stateProps: TStateProps
  let dispatchProps: TDispatchProps
  let mergedProps: TMergedProps

  function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) {
    state = firstState
    ownProps = firstOwnProps
    // @ts-ignore
    stateProps = mapStateToProps!(state, ownProps)
    // @ts-ignore
    dispatchProps = mapDispatchToProps!(dispatch, ownProps)
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    hasRunAtLeastOnce = true
    return mergedProps
  }
  function handleNewPropsAndNewState(){
    /* mapDispatchToProps の dependsOnOwnProps 次第で 新しい dispatchProps を merge するか決めている */
    ・・・省略
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }
  function handleNewProps() {
    /* mapDispatchToProps と mapStateToProps の dependsOnOwnProps 次第で、それぞれ 新しい dispatchProps と stateProps に merge するか決めている */
    ・・・省略
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }
  function handleNewState() {
    /* State に変化があったかどうかで、mergeProps するか決めている */
    ・・・省略
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

    return mergedProps
  }
  function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    const stateChanged = !areStatesEqual(nextState, state)
    state = nextState
    ownProps = nextOwnProps

    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    if (propsChanged) return handleNewProps()
    if (stateChanged) return handleNewState()
    return mergedProps
  }

  return function pureFinalPropsSelector(
    nextState: State,
    nextOwnProps: TOwnProps
  ) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
}
```

見てみたら、hasRunAtLeastOnce が false の１回目は `mapStateToProps` と `mapDispatchToProps` を使ってできた Props を merge して mergedProps として返し、hasRunAtLeastOnce が true の２回目以降は prop と state の変化次第で 違う mergedProps を返しています。

上で出てきた `childPropsSelector` は、`actualChildPropsSelector` → `actualChildProps` に引き継がれ、`actualChildProps` から `<WrappedComponent>` の props に渡されます。

ここまでの流れで、`connect` で描画がされる部分と、`connect` の `mapStateToProps` と `mapDispatchToProps` が 描画された部分に props として渡される部分まで見てみました。

一旦ここまで。