react-reduxのconnectでmergePropsを活用する

react-reduxでPresentational ComponentとContainer Componentの接続にconnect関数を使っているとひとつ困ることがあって、それは**`mapDispatchToProps`の中でステートを知ることができない**ということです。

```ts
const mapDispatchToProps = (dispatch: Dispatch<void>) => {
  borrowBook(bookId: BookId, currentUserId: UserId, price: number) {
    ...
  }
};
```
ステートに全部データが入っているのに、わざわざこうして呼び出しに引数で渡さないといけないというのも違和感が有りますが、なにより接続されるPresentational Component側でどのようなデータがpropsから渡ってきているかというのを知っていることが呼び出しの前提というのが責務上あまり良くない気がします。

なので、今回は`mergeProps`を活用して、こんな風に`mapDispatchToProps`でステートを受け取ることができるようにしてみます。このようにすることで、Componentを経由して関数へステートのデータを渡す必要がなくなります。

```ts
const mapDispatchToProps = (state: MappedState, dispatch: Dispatch<void>) => {
  borrowBook() {
    const bookId = state.books.currentBook.id;
    const currentUsrId = state.users.currentUser.id;
    const price = state.books.currentBook.price * state.orders.currentQuantity;

    ...
  }
};
```

# `mapDispatchToProps`の代わりに`mergeProps`を使う
`mergeProps`というconnect関数の第三引数は、ステートとReduxのDispatch、接続されるPresentational Componentのpropsをすべて引数として受け取り、それらをmerge（結合）して返すためのコールバックを提供しています。

この`mergeProps`を`mapDispatchToProps`の代わりに、マッピングする関数の定義場所として使うことで、ステートを参照しながらPresentational Componentへマッピングする関数の定義を行えるようになります。

```ts:HogeContainer.ts
const mapStateToProps = (state: State) => state;

const mapDispatchToProps = (dispatch: Dispatch<void>) => ({ dispatch });

const mergeProps = 
  (state: State, { dispatch }: { dispatch: Dispatch<Void> }, ownProps: OwnProps)  => ({
    ...state,
    ...ownProps,

    borrowBook() {
      ...
    }
  });

export const connectHogeContainer = 
  connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component);
```
この実装で今回やりたいことはある程度実現できていますが、このコードの微妙な点はいくつかあり、

- `mergeProps`の中で`state`と`ownProps`との結合を毎回やらないといけないのが面倒だし忘れる可能性がある
- できれば`mergeProps`の引数をもう少しスマートにしたい（例えば`{ dispatch }`を止めたい）
- `mapDispatchToProps`の内容は変わらないので、できれば定義を省略したい

など、まだ改善の余地があります。

# connect関数のラッパを作り`mergeProps`を`mapDispatchToProps`のように使う
よりDRYな実装にするために、以下のようなreact-reduxのconnectのラッパを定義しておくことで、`mapDispatchToProps`と同様の挙動で`mergeProps`を使えるようになり、mergeProps特有のコードを隠蔽することができます。`Object.assign`で渡ってくる引数をすべて結合しているのがポイントです。

```ts:customConnect.ts
import { connect as reduxConnect } from 'react-redux';
import { Dispatch } from 'redux';
import { State } from '../store';

export const connect = <S, M>(
  mapStateToProps: (state: State) => S,
  mapDispatchToProps: (state: S, dispatch: Dispatch<void>) => M
) =>
  reduxConnect(
    mapStateToProps,
    (dispatch: Dispatch<void>) => ({ dispatch }),
    (state: S, { dispatch }: { dispatch: Dispatch<void> }, ownProps: {}) =>
      Object.assign({}, state, ownProps, mapDispatchToProps(state, dispatch))
    );
```

これを使うことによって、Container Component内での`mapDispatchToProps`は第一引数に`mapStateToProps`で定義されたステートを受け取ることができるようになり、最初の実装のような`mergeProps`特有のコードが減らせてスマートになりました。

```ts:HogeContainer.ts
import { connect } from '../customConnect';
import { Dispatch } from 'redux';
import { State } from '../store';

interface MappedState {
  currentBookId: number;
  currentUserId: number;
  subtotal: number;
}

const mapStateToProps = (state: State): MappedState => {
  currentBookId: state.books.currentBook.id,
  currentUsrId: state.users.currentUser.id,
  subtotal: state.books.currentBook.price * state.orders.currentQuantity
};

interface MappedActions {
  borrowBook: () => void;
}

const mapDispatchToProps = (state: MappedState, dispatch: Dispatch<void>) => {
  borrowBook() {
    const bookId = state.currentBookId;
    const currentUsrId = state.currentUserId;
    const subtotal = state.subtotal;

    ...
  }
};

export connectHogeContainer = 
  connect<MappedState, MappedActions>(mapStateToProps, mapDispatchToProps);
```

参考: http://anect.hatenablog.com/entry/2017/06/09/151000