LoginSignup
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-28

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

redux公式のflowtypeサンプル

redux/examples/todos-flow at bc3f2aeb669f4ad5e424f6a711fd588e9bd3462e · reactjs/redux

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

// @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のstoredispatchの定義使いまわすことで型定義の恩恵を得られる。
actionやreducerでの使い方はサンプルのレポジトリを見てほしい。

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

flow-typed/react-redux_v4.x.x.js at c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6 · flowtype/flow-typed

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環境で使う場合は、以下のような型定義を使うといい。

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;

}

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

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
flow-typed/react-redux_v4.x.x.js at c4bbd91cfc455a5e061baf9e36a0e0a631eeb4b6 · flowtype/flow-typed
This gist shows how to add support for redux-thunk and redux-promise-middleware to the flowtype libdefs for redux

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11