typescript-fsaで作られるactionについて

# typescript-fsaしゅごい :sunflower:

なんでしゅごいのかは@m0aさんの[こちらの記事](https://qiita.com/m0a/items/703d64c74e43cb392a64)が分かりやすいです。
FSAって何って方は[こちら](https://github.com/redux-utilities/flux-standard-action)。

この記事はtypescript-fsaによって作られるactionについて説明します。

## Actionを作る :runner: 

actionを作るにはまず`actionCreator`を作ります

```typescript
import actionCreatorFactory from 'typescript-fsa'

const actionCreator = actionCreatorFacory()
```

ここからactionを定義していきます

```typescript
// typeが'HogeAction'かつpayloadの型がstringなFSA
const hogeAction = actionCreator<string>('hogeAction')
hogeAction.type
// 'hogeAction'
hogeAction('a')
// { type: 'hogeAction', payload: 'a' }

// payloadには型チェックが入る
hogeAction(1)
// error TS2345: Argument of type '1' is not assignable to parameter of type 'string'.

// payload無し
const hugaAction = actionCreator<void>('hugaAction')
const piyoAction = actionCreator('piyoAction')

// error
const errorAction = actionCreator<Error>('errorAction')
errorAction(new Error('hoge')) 
// {
//   type: 'errorAction',
//   payload: Error: hoge
//       at /home/kc5m/src/localhost/kc5m/fsa-saga-sample/[eval].ts:1:13
//       // 省略
//   error: true  // Error型を渡すと自動的に{error: true}が付与される(条件変更可)
// }
```

## 非同期用Actionを作る :gear: 

`typescript-fsa`には非同期処理向けに非同期用Actionをいい感じに作ってくれるAPIも用意されています。

`AsyncActionCreator`(AAC)と呼称されています。

実際にAACを定義してみます。

```typescript
// AAC定義
const hogeAsyncAction = actionCreator.async<string, number, Error>('hogeAsyncAction')
```

`actionCreator.async()`は`Params`, `Result`, `Error`(javascriptの`Error`型とは別)の3つの型から`started`, `done`, `failed`の3つのAction(FSA)を生成します。

上記の例で言うと`string`が`Params`, `number`が`Result`, `Error`が`Error`に相当します。

生成された3つのActionは非同期処理での利用を想定されています。例えば以下の非同期関数があったとします。

```typescript
const f = async (params: string): Promise<number> {
  const result = await someAsyncFunc(pars)
  if (!result) {
    throw new Error('error')
  }
  return 0
}
```

`started`は`f()`が実行されたタイミングを、`done`は`return`されたタイミングを、そして`failed`は`throw`されたタイミングをそれぞれ表現していると考えると分かりやすいかも知れません。

さて、このAACですが通常のActionではないのでそのままActionとして使えません。

```typescript
hogeAsyncAction('aaa')
// error TS2349: This expression is not callable.
```

そのためAAC内propertyに生成されたActionを使う必要があります。

```typescript
// started
hogeAsyncAction.started('a')
// { type: 'hogeAsyncAction_STARTED', payload: 'a' }
```

`started`と同じように`done`と`failed`を使うとerrorが出ます。

```typescript
// done
hogeAsyhogeAsyncAction.done(2)
// error TS2345: Argument of type '2' is not assignable to parameter of type '{ params: string; } & { result: number; }'.

// failed
hogeAsyhogeAsyncAction.failed(new Error('hoge'))
// error TS2345: Argument of type 'Error' is not assignable to parameter of type '{ params: string; } & { error: Error; }'.
```

AACが作る`done`, `failed`のpayloadの型は拡張されていて、payloadの中に`started`に渡したpayloadが含まれるようになっています。
上記の例で言うとerrorにも書かれていますが`done`は`{ params: string } & { result: number }`型に、`failed`は`{ params: string } & { error: Error }`型になります。

```typescript
hogeAsyncAction.done({ params: 'a', result: 1 })
// { type: 'hogeAsyncAction_DONE', payload: { params: 'a', result: 1 }}

hogeAsyncAction.failed({ params: 'a', error: new Error('aaa') })
// {
//   type: 'hogeAsyncAction_FAILED',
//   payload: {
//     error: Error: aaa
//         at /home/kc5m/src/localhost/kc5m/fsa-saga-sample/[eval].ts:1:33
//         // 省略
//     params: 'a'
//   },
//   error: true
// }
```

まとめると以下のようなります。

`actionCreator.async<Params, Result, Error>()`で作られるactionのpayloadの型は

- `started`: `Params`
- `done`: `{ params: Params } & { result: Result }`
- `failed`: `{ params: Params } & { error: Error }`

また、`done`は`Result`に`Error`型(`new Error()`とか)が来ても`error: true`になりません。`FAILED`は`Error`に`Error`型以外が来ても`error: true`になります。

```typescript
const fugaAsyncAction = actionCreator.async<string, Error, string>('fugaAsyncAction')
fugaAsyncAction.done({result: new Error('aaa'), params: 'a'})
// {
//   type: 'fugaAsyncAction_DONE',
//   payload: {
//     result: Error: aaa
//         at /home/kc5m/src/localhost/kc5m/fsa-saga-sample/[eval].ts:1:32
//         // 省略
//     params: 'a'
//   }
// }

fugaAsyncAction.failed({error: 'a', params: 'a'})
// {
//   type: 'fugaAsyncAction_FAILED',
//   payload: { error: 'a', params: 'a' },
//   error: true
// }
```

## 最後に :rocket: 

`typescript-fsa`を使うことによって、型チェックが走るFSAの定義及び非同期処理用に複数のFSAを少ない行数で定義できます。
これ単体だけでも便利ですが、同じ作者より提供されている以下のCompanion Packagesを併用することにより更に便利に使うことができます(リンク省略)。

- `typescript-fsa-redux-saga`  
- `typescript-fsa-redux-observable`
- `typescript-fsa-redux-thunk`
- `typescript-fsa-reducers`

以上 :dash: