こんにちは、HRBrain 鈴木です 普段はReact + TypeScriptで社のサービス開発をしています!
およそ2ヶ月前TypeScript 3.4 がリリースされました(もうすぐ 3.5 も出ます!)
少し時間が経ってしまいましたが、新機能の「const assertion」をつかって Redux の Action をちょっぴりラクに書く方法をご紹介します
const assertion とは
TypeScript には自分の型を定義する機能があります
const hoge = 'Hoge' type Hoge = typeof hoge // -> 'Hoge' 型
上記の例では、一度変数に入れた後 typeof
使用して型を取り出すと string
ではなく 'Hoge'
型になります
しかし、この方法は弱点がありました hoge
を別の変数に代入すると型の強制力が無くなって string
になってしまいます
const hoge = 'Hoge' const withHoge = (o: object) => ({ ...o, hoge: hoge }) // -> (typeof o) & { hoge: string }
ですので、これには型アノテーションを付ける必要があります
- const withHoge = (o: object) => ({ ...o, hoge: hoge }) // -> (typeof o) & { hoge: string } + const withHoge = <T extends object>(o: T): T & { hoge: typeof hoge } => ({ ...o, hoge: hoge }) // -> (typeof o) & { hoge: 'Hoge' }
これを一々書いていたら大変です そこで「const assertion」を使うと以下のように書けます
const hoge = 'Hoge' as const const withHoge = (o: object) => ({ ...o, hoge: hoge }) // -> (typeof o) & 'Hoge'
再代入しても 'Hoge'
型を維持できるようになりました
これを応用して Redux の Action を書いてみましょう!
const assertion で Action を定義する
先程の例を応用して ActionTypes を作ってみます
const Increment = 'Increment' as const const Decrement = 'Decrement' as const const Replace = 'Replace' as const
「const assertion」を使用したので別の変数に代入しても型が失われないようになりました
次に Action を定義します
const increment = () => ({ type: Increment }) const decrement = () => ({ type: Decrement }) const replace = (payload: number) => ({ type: Replace, payload })
型アノテーションも無くシンプルに書けました 最後に Actions 型を作りましょう
type Actions = ReturnType<typeof increment | typeof decrement | typeof replace> // -> { type: 'Increment' } | { type: 'Decrement' } | { type: 'Replace'; payload: number }
これもかなりシンプルです!
ReturnType
を使って Action から型を推論させることでわざわざ Action 一つ一つの型を定義せずとも Union 型を作ることができます!
最終的に State やら Reducer やらを追加すると以下のようになります メチャメチャシンプルです
/** * State */ type State = { count: number } /** * Actions */ const Increment = 'Increment' as const const Decrement = 'Decrement' as const const Replace = 'Replace' as const /** * Actions Creators */ const increment = () => ({ type: Increment }) const decrement = () => ({ type: Decrement }) const replace = (payload: number) => ({ type: Replace, payload }) type Actions = ReturnType<typeof increment | typeof decrement | typeof replace> /** * Reducer */ const initialState: State = { count: 0 } const reducer = (state: State | null, action: Actions):State => { if (!state) return initialState switch (action.type) { case Increment: return { count: state.count + 1 } case Decrement: return { count: state.count - 1 } case Replace: return { count: action.payload } default: throw new Error('Invalid action type') } }
最後に
コードがシンプルだとテンション上がりますよね
HRBrainではそんな「Simple is more」なコードを一緒に書いてくれる人を募集しています!