reduxをtypescriptで使うならこれを使うしかない。(typescript-fsaがすごい)


# ReduxとTypeScriptっていまいち相性が良くない？

TypeScriptとReact.jsって相性が良いですよね。
実際Reactを使うのに[TypeScriptとかflowを推奨している気がします](https://facebook.github.io/react/docs/typechecking-with-proptypes.html)。
Reactの学習を始めるのにも補助輪として有効ではないかと思います。
その辺は 古いですが
「[TypeScriptを使ってreactのチュートリアルを進めると捗るかなと思った。](http://qiita.com/m0a/items/d723259cdeebe382b5f6)」
を見ていただければご理解いただけると思います。

さて、しかしながらです。この調子で[ReduxもTypeScriptで学習初めたら捗るかと思いましたが](http://qiita.com/m0a/items/cdeb87ccc16bba3c0927) **上手く行きませんでした。**

理由の一つとして

**reduxにおいて``Action`` は概念として重要なものですが その実態は ``type`` プロパティを持つobjectでしかない。** という点があります。

これが辛い。

TypeScriptにおいてどの様にActionを定義するべきか指針となる情報がないのです。
とりあえず、私は以下のようにActionを定義することにしました

```typescript:actions.ts
export type Action =
    {
        type: 'INIT';
        payload: undefined;
    } |
    {
        type: 'FETCH_MAIN_FEEDS';
        payload: undefined;
    } |
    {
        type: 'SET_MAIN_FFEDS';
        payload: comm.Contentlist;
    } |
    {
        type: 'FETCH_ACCOUNT';
        payload: FetchAccountPayload;
    } |
    {
        type: 'SET_ACCOUNTS';
        payload: comm.Account[];
    } |
    {
        type: 'FETCH_LOGIN_TOKEN';
        payload: undefined;
    } |
    {
        type: 'SET_LOGIN_TOKEN';
        payload: string;
    } |
// 省略
```

この書き方は[F8App](https://github.com/fbsamples/f8app/blob/master/js/actions/types.js)を参考にしています。とりあえずこの書き方はreducerを書く時に有効でした。

<img width="763" alt="index_ts_—_taiya-ki-client.png" src="https://qiita-image-store.s3.amazonaws.com/0/3844/f3f4182f-5ef4-209a-0811-d0006afd8f5e.png">

上記のようにtypescriptのTypeGuard機能によってaction.typeが ``SET_MAIN_FFEDS``のルートで
action.payloadが``Contentlist``だと推論してくれるわけです。

良さそうじゃないですか。

でもこの書き方だとactionCreatorを作るのがしんどくなります。
jsにおいてはactionを毎回書くのがしんどいのでactionCreatorという **actionを返す関数** を作るのですが
TypeScriptでは、間違いを指摘してくれるのでactionを直接dispatchに書きたくなります(※個人の感想です)。
と言うか上記の書き方だと変更があった場合にactionCreatorとActionの両方の修正が必要となり冗長になります。
かと言ってactionCreatorのみの定義だと、上記のようなTypeGuardも使えなくなります。

辛い。

そんな中見つけたのが、 ``typescript-fsa``  です。

# ``typescript-fsa`` の使いかた

とりあえずです。

あなたのreduxを使ったプロジェクトにプロジェクトに追加しましょう

```
$ yarn add typescript-fsa
$ yarn add typescript-fsa-reducers
```

多分 ``typescript-fsa-reducers``も使うことになります。reducerを使う上での便利ライブラリです。

## actionの変更

* before

```typescript:before
export type Action =
    {
        type: 'INIT';
        payload: undefined;
    } |
    {
        type: 'FETCH_MAIN_FEEDS';
        payload: undefined;
    } |
    {
        type: 'SET_MAIN_FFEDS';
        payload: comm.Contentlist;
    } |
    {
        type: 'FETCH_ACCOUNT';
        payload: FetchAccountPayload;
    } |
    {
        type: 'SET_ACCOUNTS';
        payload: comm.Account[];
    };
// 一部のみ

```

* after

```typescript:after
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

export const init = actionCreator('INIT');
export const fetchMainFeeds = actionCreator('FETCH_MAIN_FEEDS');
export const setMainFeeds = actionCreator<comm.Contentlist>('SET_MAIN_FFEDS');
export const fetchAccount = actionCreator<FetchAccountPayload​​>('FETCH_ACCOUNT');
export const setAccounts = actionCreator<comm.Account[]>('SET_ACCOUNTS');

// 一部のみ

```

こんな感じでactionCreatorを簡単に作れます。
これでちゃんと補完機能を使えるのでしょうか？ 
ポイントは``actionCreator<ペイロードの型>`` という形式です。
actionCreatorなので当然dispatchするときはこんな書き方になります

```typescript
props.dispatch(actions.init());
```

## reducerの変更

さて前の書き方と変わってちゃんとtypeGuardが使えるのでしょうか？
結論：使えました。


* before

```typescript:before
export function loginToken(state: LoginToken = new LoginToken(), action: Action) {
    switch (action.type) {
        case 'SET_LOGIN_TOKEN':
            state = state.setLoginToken(action.payload);
            return state;
        case 'SET_LOGIN_INFO':
            state = state.setMyAccount(action.payload);
            return state;
        case 'LOGOUT':
            state = state.logout();
            return state;
        default:
            return state;
    }
}

```

* after

```typescript:after
import { reducerWithInitialState } from 'typescript-fsa-reducers';

export const loginToken = reducerWithInitialState(new LoginToken())
    .case(actions.setLoginToken, (state, payload) => (state.setLoginToken(payload)))
    .case(actions.setLoginInfo​​ , (state, payload) => (state.setMyAccount(payload)))
    .case(actions.logout, (state) => (state.logout()));

```

上記２つは全く同じ挙動でした。
ちゃんとペイロードの型が推論できている様子も貼っておきます。

<img width="762" alt="index_ts_—_taiya-ki-client2.png" src="https://qiita-image-store.s3.amazonaws.com/0/3844/b43f8496-a406-9c13-e8de-f6a4c60c1f4a.png">

# 仕組み

作成したactionCreaterの型を見てみると次のようになっています。

<img width="719" alt="index_ts_—_taiya-ki-client3.png" src="https://qiita-image-store.s3.amazonaws.com/0/3844/77ab27ea-4b78-0469-5a2f-9950b8e24eb1.png">

actionCreater内にペイロードの型情報を持っています。

```
case<P>(actionCreator: ActionCreator<P>, handler: Handler<InS, OutS, P>): ReducerBuilder<InS, OutS>;
```
caseの引数に渡される``actionCreator`` からPが推論できるのでhandlerの引数にペイロードの型情報（P）が渡されるわけです。

わかってしまえばなんてことはないですね。

# 参考

https://www.triplet.fi/blog/type-safe-flux-standard-actions-fsa-in-react-using-typescript-fsa/