【redux】as constでaction creatorからActionの型を簡単につくる

3.4以前では、型付をするのにas assertionでLiteralTypeを使う必要があり面倒でした。が、TS3.4から登場した`as const`によってreduxの型付はとても手軽になりました。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15319/72b8da14-dc96-ef6b-76d3-fae0db10fd87.png" height=500 />


まずActionCreatorを作ります。このときtype属性に`as const`を付けるよりオブジェクトに`as const`を付ける方がすべての属性がreadonlyになるのでオススメします。

```ts
const increment = (payload: number) =>
  ({
    type: 'increment',
    payload,
  } as const)
```

Action型を作るのは以下のような型定義が必要になります。やっていることは単純でそれぞれの`ActionCreator` から `ReturnType`で戻り値の型を取り出しそのUnion型を作ります。`AnyAction` は必須ではないですが、 `type`プロパティを必ず含んでいることを保証することができます。

```ts
import { AnyAction } from 'redux'

export type ActionsType<ActionCreators extends object> = {
  [Key in keyof ActionCreators]: ActionCreators[Key] extends (
    ...args: any[]
  ) => AnyAction
    ? ReturnType<ActionCreators[Key]>
    : never
}

export type ActionType<
  ActionCreators extends object,
  Actions = ActionsType<ActionCreators>
> = { [Key in keyof Actions]: Actions[Key] }[keyof Actions]
```

使い方としては、`as const`付きでActionCreatorを作成し、`ActionType<typeof actions>`でAction型を作成できます。

```actions.ts
export const increment = (payload: number) =>
  ({
    type: 'increment',
    payload,
  } as const)

export const decrement = (payload: number) =>
  ({
    type: 'decrement',
    payload,
  } as const)

export const typo = (payload: string) =>
  ({
    // typo...
    typo: 'typo',
    payload,
  } as const)
```

```types.ts
import { ActionType } from './redux-actions-type'
import * as actions from './actions'

type Action = ActionType<typeof actions>
/**
 * type Action = {
 *   type: "increment";
 *   payload: number;
 * } | {
 *   type: "decrement";
 *   payload: number;
 * }
 */
```

`type`プロパティだけでなく、payloadが正しく推論されていることがわかります。
また、`type`プロパティがないものは`never`型になるので、Action型には現れることはないので安心して使えます。

あとは、普通に`reducer`などで使うだけです。

```reducer.ts
import { Action } from './types'

type State = {
  count: number
}

export const reducer = (state: State = { count: 0 }, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + action.payload }
    case 'decrement':
      // 主題とはずれるが戻り値のStateを省略するとこの余計な`hoge`プロパティを検知できない。
      // reduxのReducer型を使っても同じように検知されないので注意
      // return { count: state.count - action.payload, hoge: 1 }
      return { count: state.count - action.payload }
    // Type Error...
    // case 'typo':
    //   return { count: action.payload }
    default:
      return state
  }
}
```

また、`Action`はreducerごとに別々の型にするのではなく、プロジェクトで一つの型にした方が取り回しが楽です。

毎回この定義を書くは面倒なので、ライブラリからインストールできます。(スターするのはよい選択肢です🤭)
[akameco/redux-actions-type: easy typing of redux action](https://github.com/akameco/redux-actions-type)

```
$ npm install redux-actions-type
or
$ yarn add redux-actions-type
```

```types.ts
import { ActionType } from 'redux-actions-type'
import * as actions from './actions'

export type Action = ActionType<typeof actions>
```

また付属で`Actions`という型をexportできます。
これは個別のAction型を取れるので使う必要があれば(あまりそういうシーンはありませんが)使うことができます。たいていはActionCreater自身のReturnTypeで済むでしょう。

```ts
import { ActionsType } from 'redux-actions-type'
import * as actions from './actions'

export type Actions = ActionsType<typeof actions>
/**
 * type IncrementAction = {
 *   type: "increment";
 *   payload: number;
 * }
 */
type IncrementAction = Actions['increment']
```

TypeScriptの型非常に便利でいいですね。これからの進化にも期待です。


<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="en" dir="ltr">TypeScript 3.4 <a href="https://t.co/wY45XfZPV2">pic.twitter.com/wY45XfZPV2</a></p>&mdash; あかめ@孤独に歩め。悪をなさず、求めるところは少なく。林の中の象のように.js (@akameco) <a href="https://twitter.com/akameco/status/1119994171866042374?ref_src=twsrc%5Etfw">April 21, 2019</a></blockquote>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">akameco/redux-actions-type: easy typing of redux action <a href="https://t.co/HpCKrmA84i">https://t.co/HpCKrmA84i</a> 型のない環境から手軽にreduxのActionに型付けるところまで持っていくやつです <a href="https://t.co/Hlup7e8Jjt">pic.twitter.com/Hlup7e8Jjt</a></p>&mdash; あかめ@孤独に歩め。悪をなさず、求めるところは少なく。林の中の象のように.js (@akameco) <a href="https://twitter.com/akameco/status/1106170260539891712?ref_src=twsrc%5Etfw">March 14, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>