redux
とflowtype
を組み合わせたサンプルはredux
のサンプルにあるが、redux-thunk
との組み合わせの例はない。この記事では、redux
とflowtype
のサンプルを示したあと、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のstore
とdispatch
の定義使いまわすことで型定義の恩恵を得られる。
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