React, Reduxのお型付け

[ブログ](https://mrsekut.site)を消そうかと思うので、一部の記事をQiitaへ移行してきました。
2019/2/21に投稿したものです。


React, ReduxのTypeScriptでの型付けの個人的ベストプラクティスです。
勉強会で発表したので簡単にブログにまとめます。


# はじめに
スライドは以下のページにあります。

【参考】[React, Reduxのお型付け](https://scrapbox.io/public-mrsekut/React,_Redux%E3%81%AE%E3%81%8A%E5%9E%8B%E4%BB%98%E3%81%91)

# コンセプト
型の付け方には様々な方法があると思いますが、今回は以下のようなコンセプトで進めていきます。

- 楽に
- 安全に
- 外部ライブラリに頼らずに

「楽に」というのは、型の記述量を減らすという意味です。
今実際開発しているプロダクトでは、一つのactionを追加するたびに、型を明示するために追記しないといけない部分が複数箇所にわたってしまっていました。
今回は、それを改善するためにいろいろ工夫したものを紹介します。

外部ライブラリというのは、typescript-fsaやtypesafe-actionsやredux-actionなどのことですが、メンテナンスなどの事も考えて、今回は何も使わずに型を付ける方法を紹介します。

# 実はハンズオンです

コードは以下です。

【参考】[mrsekut/react-redux-with-typescript-handson](https://github.com/mrsekut/react-redux-with-typescript-handson/tree/all-any-type)

とても小さいReduxプロダクトを用意しました。
「+」と「-」があって数字を増やすだけのものです。

「all-any-type」というブランチがあります。
これはすでに動く状態ですが、全てany型になっています。
これを一緒に型安全なものにしていきます。

## 準備
「all-any-type」ブランチを指定してクローンしてください。

`$ git clone -b all-any-type https://github.com/mrsekut/react-redux-with-typescript-handson.git`

`$ cd react-redux-with-typescript-handson`

reactなどの依存パッケージをインストールします。
`$ npm i`

TypeScriptを監視状態でコンパイルします。
`$ npm run tsc`

別のターミナルで、以下を実行するとブラウザで動きを確認します。
型をつけるだけなので最初から最後まで目に見える変化はありません。
`$ npm start`

# TypeScriptのキホン
軽くTypeScriptの型の基本の話をします。
必要のない方は飛ばしてください。

## とりあえずこれさえ知っておけば大丈夫
TypeScriptの型システムの話は、潜り込むとどこまでも深くて大変ですが、まずは以下の4種類ほど知っておけば耐えます。

- number: 42とか
- string: “hoge”とか
- boolean: true, falseとか
- any
    - なんでもいけるやつ

数値は整数や浮動小数点数などの区別はなく、同じnumberで型を付けます。

anyは万能型で、何にでも対応できる型です。
ただし、これだけだと型のパワーの恩恵を受けられなくなるので、使うのは極力避けたいです。

## 関数の型の書き方
JSはすべてオブジェクトなこともあり、関数の型の書き方はいくつかあるのですが、今回は以下の形に統一します。

`(任意の引数名: 引数の型) => 戻り値の型`

```ts
// number型の引数を一つ取って、戻り値のない関数
addFunc: (num: number) => void;
```

## 型を自作する

TypeScriptで型を自作するには2つの方法があります。
微妙な差はありますが、まずは気にしなくても大丈夫です。

### “type”で型に別名を付ける
```ts
// name, ageを持ったオブジェクトに「Person」と命名
type Person = {
  name: string;
  age: number;
};
```

### “interface”でクラスやオブジェクトの仕様を決める
```ts
interface Person {
  name: string;
  age: number;
}
```

### ちなみにVSCodeでは

VSCodeでは、変数はhoverするとその型を確認できますが、typeで作った型では中身を見れるのでちょっと便利です。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/f816c2f5-e6a1-7491-96ba-fa135fac39a4.png)

interfaceでは見れません。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/eac16165-0976-2835-8444-652504a04079.png)

typeとinterfaceの違いが知りたい方は以下の記事などを参考にしてみてください。

【参考】

- [Interface vs Type alias in TypeScript 2.7 – Martin Hochel – Medium](https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c)
- [TypeScriptのInterfaceとType aliasの比較 – Qiita](https://qiita.com/tkrkt/items/d01b96363e58a7df830e)

# では、型付けしていきましょう

ここから実際に、[これ](https://github.com/mrsekut/react-redux-with-typescript-handson)に型を付けていきます。
実際に型を付け終わったものは[別のブランチ](https://github.com/mrsekut/react-redux-with-typescript-handson/tree/added-types)にあります。

# Presentational Componentに型を付ける
Presentational Componentというのは、Reduxと接続していない小さなコンポーネントたちのことを指します。
プロダクトの大半がこのコンポーネントになります。

## Counter/index.tsx
[src/components/Counter/index.tsx](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/components/Counter/index.tsx)

数値を表示するだけのコンポーネントです。
以下のように型を付けます。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/11400d13-c938-3bb6-0135-898497e47403.png)
@types/reactとして用意されている`React.FC<T>`型を使います。
FCはFunctional Componentの略ですね。

`T`の部分にはプロパティを定義した自作の型をはめ込みます。
ここでは、親から受け取るnumber型のnumを書いています。

propsは基本的に書き換えることはないので、readonlyで縛ることでより頑強になります。

`Readonly<T>`というのはTypeScriptに用意されている型で、Tのプロパティをすべてreadonlyにした型をつくります。

つまり、以下のように全プロパティに「readonly」と書いても同じです。
<img width="207" alt="5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/b2c86f00-3461-0056-981d-5eff3e10ada3.png">

ですが、全部に全部「readonly」と書くのも面倒なので、Readonly<T>で囲うことで少し楽ができます。
ホバーすると全く同じ様に型が当たっているのがわかるかと思います。
<img width="338" alt="66.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/dfc3398b-c0bb-26a5-79be-145b9eb5ddd4.png">


簡単ですね。
以上のようにしてCounterコンポーネントに型が付きました。
ホバーすると型が適用されているのを確認できます。
<img width="513" alt="7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/04831a9b-c8d7-7e1d-1dd8-85964bde6850.png">

この調子で型付けをしていきます。

## Button/index.tsx

[src/components/Button/index.tsx](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/components/Button/index.tsx)

その名の通り、ボタンのためのコンポーネントです。
React.FC<T>を使うなど、さきほどとだいたい同じです。
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/e5cc0841-27e2-884b-a024-f26dd5114e38.png)

違う部分は任意の関数を使っている点と、childrenを使っている点です。

ButtonPropsの中の「onClick?」の疑問符は任意のプロパティであることを示します。
このButtonコンポーネントを使う時にonClick属性はあってもなくても良いということです。

## @types/reactで用意されているカタガタ
@types/reactには、似たような型がいくつかありますが、一部紹介します。。

- `React.ReactElement`
    - divやpのような仮想DOMを表す
    - HTMLElementのReact版のようなもの
- `React.ReactChild`
    - ReactElementもしくはstringもしくはnumberを表す
- `React.ReactNode`
    - ReactElement, Fragment, Portals, primitiveな型

いろいろありますが、childrenに対しては、React.ReactChildを使っておけば問題なさそうです。

# 型を付けて何が嬉しいのか
閑話休題。
そもそもの話ですが、Reactを開発する上でコンポーネントに型を付けて何が嬉しいのかについてです。

共同開発をするときや、外部ライブラリとして使うコンポーネントがあるときに、型があることでそのコンポーネントの作者の意図と反した使い方をするのを防ぐことができます。

いまさっき作ったButtonコンポーネントの仕様はButtonPropsで定義しましたが、これと異なる使われ方をするとコンパイルエラーで知らせてくれます。
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/e04ca2e3-a253-081c-379c-215591f797f5.png)


# Reduxに型を付ける
では、次にRedux側に型を付けていきます。
今回はDucksデザインパターンを採用しており、actionやreducerはmodule.tsという1つのファイルの中に定義しています。

# actionに型を付ける

[src/modules/module.ts](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/modules/module.ts)

ここで用意するactionは「+」「-」各ボタンを押したときに実行されるものです。
asはキャストです。

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/2aa2d866-12d4-b1f5-c07c-789b03cd3d38.png)

予め作っておいたActionTypesでキャストすることで型が付きます。

この書き方をすることで、わざわざactionを書くたびにそれようの型を書かなくて済みます。

今までは以下のように書いていました。
一つのaction一つのinterfaceを作っていたのでとても冗長になってしまっていました。
<img width="579" alt="11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/a6b55dba-e145-f70e-cbcb-1935e10267af.png">


## module全体のactionに型を付ける

[src/modules/module.ts](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/modules/module.ts)

あとでreducerに渡すためにmodule全体のactionに型を付けます。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/b824866b-97d7-37c2-c1a0-9ca6ac506740.png)

ここではTypeScriptのちょっとテクった書き方をしています。

`typeof hoge`はhogeの型を表します。
ここではactionそれぞれの関数の型になります。

`ReturnTypes<T>`もTypeScriptが用意しているもので、Tが関数の型の場合、その戻り値の型になります。

ReturnTypes<T>自体はconditional typesを使って以下のように定義されています。

```ts
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
```

パイプ「|」はUnion typesです。

# global stateに型を付ける

[src/modules/module.ts](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/modules/module.ts)

global stateの型を付けます。
stateの初期値の宣言などでも使います。

これもpropsのときと同じ様にreadonlyを付けています。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/82ba9f76-6861-4197-ae4b-6327616527cb.png)


# Reducerに型を付ける

[src/modules/module.ts](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/modules/module.ts)

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/edda0df0-5b81-1896-3521-2edc61387184.png)

reduxが用意している`Reducer<S, A>`型と先ほど定義したmoduleのactionと、global stateの型を使います。

もう一点工夫している箇所が、上記のコードの最後のdefaultの部分でnever型を使っている点です。

`never`には`never`型の値しか入りません。
コレを使って、union typesで定義したMainAction型に対して、switch文のcaseの漏れを防ぐことができます。

例として、今回のコードの一つの分岐をコメントアウトすると、コンパイルエラーになるのがわかります。
<img width="541" alt="15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/83346f81-f883-f2d7-42a4-f430288688e9.png">

今回の例では、actionは2つしかないので、漏れが出ることはないと思いますが、プロダクトが大きくなってくるとこの分岐が増えていきます。
最初にreducerを作る時点でこの一行を書いておくことで、actionが増えてきてもうっかり書き忘れることを防ぐことができます。

【参考】

- [TypeScript 2.0のneverでTagged union typesの絞込を漏れ無くチェックする – Qiita](https://qiita.com/wadahiro/items/9ec4af968a7314402499)
- [Never Type – TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/type-system/never)

# Storeに型をつける

[src/store.ts](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/all-any-type/src/store.tsx)

store自体に型をつけるわけではないですが、各moduleで定義した型をここでまとめます。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/f891a37e-4ec1-69fa-d933-034e5328543d.png)

一番下の行のActionはreduxで用意されている型です。

# Container Componentに型をつける

[src/containers/index.tsx](https://github.com/mrsekut/react-redux-with-typescript-handson/blob/added-types/src/containers/index.tsx)

これで最後です。
container componentというのはReduxと接続しているコンポーネントのことです。

まずはstateやactionをpropsに変換する関数に型を付けます。
`Dispatch<T>`はreduxで用意されている型です。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/7e975e2d-299d-ede1-3306-ad1a498b8113.png)

次に、containerのpropsに型を付けます。
上記2つの関数の`ReturnTypes`を使います。
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/a7cfc052-69c9-4967-4113-a7b214d028f4.png)

この型をReact.FC<T>を使ってコンポーネントに当てます。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186534/0b564858-2415-5525-402d-a87b64eb23f0.png)


# おわり
お疲れ様でした。
これでプロダクト全体に型が行き渡り、再び息を吹き返しました。
こんな感じで型を当てていくと、楽に、安全に、当てられるのではないでしょうか。

# 参考
- [piotrwitek/react-redux-typescript-guide: The complete guide to static typing in “React & Redux” apps using TypeScript](https://github.com/piotrwitek/react-redux-typescript-guide)
- [typescript-fsaに頼らないReact × Redux – ログミーTech](https://logmi.jp/tech/articles/320496)
- [React Reduxを型で縛ってみよう – Tech Blog – Recruit Lifestyle Engineer](https://engineer.recruit-lifestyle.co.jp/techblog/2019-01-31-how-to-bind-react-with-Flow/)