flowtypeでredux-thunkの型定義を使う

`redux`と`flowtype`を組み合わせたサンプルは`redux`のサンプルにあるが、`redux-thunk`との組み合わせの例はない。この記事では、`redux`と`flowtype`のサンプルを示したあと、`redux-thunk`で型の恩恵を受ける方法を紹介する。


## redux公式のflowtypeサンプル
[redux/examples/todos-flow at bc3f2aeb669f4ad5e424f6a711fd588e9bd3462e · reactjs/redux](https://github.com/reactjs/redux/tree/bc3f2aeb669f4ad5e424f6a711fd588e9bd3462e/examples/todos-flow)

`types`以下はこうなっている。

```js
// @flow
import type { Store as ReduxStore, Dispatch as ReduxDispatch } from 'redux'

export type Id = number;

export type Text = string;

export type Todo = {
  id: Id,
  text: Text,
  completed: boolean
};

export type VisibilityFilter =
    'SHOW_ALL'
  | 'SHOW_ACTIVE'
  | 'SHOW_COMPLETED'
  ;

export type Todos = Array<Todo>;

export type State = {
  todos: Todos,
  visibilityFilter: VisibilityFilter
};

export type Action =
    { type: 'ADD_TODO', id: Id, text: Text }
  | { type: 'TOGGLE_TODO', id: Id }
  | { type: 'SET_VISIBILITY_FILTER', filter: VisibilityFilter }
  ;

export type Store = ReduxStore<State, Action>;

export type Dispatch = ReduxDispatch<Action>;
```

reduxの`store`と`dispatch`の定義使いまわすことで型定義の恩恵を得られる。
actionやreducerでの使い方はサンプルのレポジトリを見てほしい。

ここで使われている型定義はflow-typedと同等であり、以下のような型定義である。

[flow-typed/react-redux_v4.x.x.js at c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6 · flowtype/flow-typed](https://github.com/flowtype/flow-typed/blob/c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6/definitions/npm/react-redux_v4.x.x/flow_v0.30.x-/react-redux_v4.x.x.js)

```js
declare module 'redux' {

  /*
    S = State
    A = Action
  */

  declare type Dispatch<A: { type: $Subtype<string> }> = (action: A) => A;

  declare type MiddlewareAPI<S, A> = {
    dispatch: Dispatch<A>;
    getState(): S;
  };

  declare type Store<S, A> = {
    // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
    dispatch: Dispatch<A>;
    getState(): S;
    subscribe(listener: () => void): () => void;
    replaceReducer(nextReducer: Reducer<S, A>): void
  };

  declare type Reducer<S, A> = (state: S, action: A) => S;

  declare type Middleware<S, A> =
    (api: MiddlewareAPI<S, A>) =>
      (next: Dispatch<A>) => Dispatch<A>;

  declare type StoreCreator<S, A> = {
    (reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
    (reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
  };

  declare type StoreEnhancer<S, A> = (next: StoreCreator<S, A>) => StoreCreator<S, A>;

  declare function createStore<S, A>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
  declare function createStore<S, A>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;

  declare function applyMiddleware<S, A>(...middlewares: Array<Middleware<S, A>>): StoreEnhancer<S, A>;

  declare type ActionCreator<A, B> = (...args: Array<B>) => A;
  declare type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };

  declare function bindActionCreators<A, C: ActionCreator<A, any>>(actionCreator: C, dispatch: Dispatch<A>): C;
  declare function bindActionCreators<A, K, C: ActionCreators<K, A>>(actionCreators: C, dispatch: Dispatch<A>): C;

  declare function combineReducers<O: Object, A>(reducers: O): Reducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;

  declare function compose<S, A>(...fns: Array<StoreEnhancer<S, A>>): Function;

}

```

しかし、redux-thunkを導入すると整合性が取れなくなる。
見てわかると思うが、DispatchがActionしか取らないからだ。


## redux-thunk使用時の型定義
redux-thunkとflowtype環境で使う場合は、以下のような型定義を使うといい。

```js
declare module 'redux' {

  /*
    S = State
    A = Action
  */

  declare type ThunkAction<S, R> = (dispatch: Dispatch<S, any>, getState: () => S) => R;
  declare type ThunkDispatch<S> = <R>(action: ThunkAction<S, R>) => R;
  declare type PlainDispatch<A: {type: $Subtype<string>}> = (action: A) => A;
  declare type Dispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S>

  declare type MiddlewareAPI<S, A> = {
    dispatch: Dispatch<S, A>;
    getState(): S;
  };

  declare type Store<S, A> = {
    // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
    dispatch: Dispatch<S, A>;
    getState(): S;
    subscribe(listener: () => void): () => void;
    replaceReducer(nextReducer: Reducer<S, A>): void
  };

  declare type Reducer<S, A> = (state: S, action: A) => S;

  declare type Middleware<S, A> =
    (api: MiddlewareAPI<S, A>) =>
      (next: Dispatch<S, A>) => Dispatch<S, A>;

  declare type StoreCreator<S, A> = {
    (reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
    (reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
  };

  declare type StoreEnhancer<S, A> = (next: StoreCreator<S, A>) => StoreCreator<S, A>;

  declare function createStore<S, A>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A>): Store<S, A>;
  declare function createStore<S, A>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A>): Store<S, A>;

  declare function applyMiddleware<S, A>(...middlewares: Array<Middleware<S, A>>): StoreEnhancer<S, A>;

  declare type ActionCreator<A, B> = (...args: Array<B>) => A;
  declare type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };

  declare function bindActionCreators<S, A, C: ActionCreator<A, any>>(actionCreator: C, dispatch: Dispatch<S, A>): C;
  declare function bindActionCreators<S, A, K, C: ActionCreators<K, A>>(actionCreators: C, dispatch: Dispatch<S, A>): C;

  declare function combineReducers<O: Object, A>(reducers: O): Reducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;

  declare function compose<S, A>(...fns: Array<StoreEnhancer<S, A>>): Function;

}
```

上書きの仕方がちょっと変わって、以下のようになる。

```js
import type {Store as ReduxStore, Dispatch as ReduxDispatch} from 'redux';

// 略

export type Dispatch = ReduxDispatch<State, Action>;
export type Store = ReduxStore<State, Action>;
```

これで`redux-thunk`環境でもflowtypeの恩恵を受けられるようになる。

## 参考

[redux/examples/todos-flow at bc3f2aeb669f4ad5e424f6a711fd588e9bd3462e · reactjs/redux](https://github.com/reactjs/redux/tree/bc3f2aeb669f4ad5e424f6a711fd588e9bd3462e/examples/todos-flow)
[flow-typed/react-redux_v4.x.x.js at c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6 · flowtype/flow-typed](https://github.com/flowtype/flow-typed/blob/c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6/definitions/npm/react-redux_v4.x.x/flow_v0.30.x-/react-redux_v4.x.x.js)
[This gist shows how to add support for redux-thunk and redux-promise-middleware to the flowtype libdefs for redux](https://gist.github.com/pcardune/db6b4494a0c37e0c2ed5677d53dbd17d)