Redux + TypeScript 周りの調査
最近 Redux に手を伸ばし始めました。JavaScript で経験を積まずにいきなり TypeScript から始めたせいか無駄に試行錯誤したりハマっている気がしますが、めげずに界隈の状況把握から進めていきたいと思います。
今回は主に typesafe-actions と typescript-fsa を 1日 触った感じを軽く書いていきます。特に網羅的には書いていません。
side-effect 系ライブラリ
今回は深くは触れませんが、前提として Redux の action の dispatch に際しての副作用を提供するライブラリの代表的なところを挙げていきます。
この中では redux-thunk と redux-observable を少し使ってみました。redux-observable を利用するには RxJS をある程度触って理解することが必須となります。そして一応書けたコードを見て「これって保守性どうなのだろう」と思わされたので、もう少しこの辺は調査を進めたいところです。
しかし React の勉強をして Redux はそれとはまた別の勉強が必要になり、さらに RxJS の勉強もしないといけないとなると、新しくアサインされた人には辛いプロジェクトになるかもしれないですね(というより保守人員を求めるのであれば Redux ってどうなんでしょうね)。
action 系ライブラリ
action の定義を楽にしたり定型化したりするライブラリですね。FSA(Flax Standard Action) に基づいた設計の action を生成するものをよく見かけます。
- typescript-fsa (star: 290, npm: 9k/week, last update: 4 month)
- README に redux-observable のサンプルがある
- サポートライブラリに redux-thunk と redux-saga と redux-observable を扱うものがある
- typesafe-actions (star: 361, npm: 9k/week, last update: 3 month)
- こちらの方が微妙に勢いがある
- redux-observable のサンプルしか見つからない
という 2 つのライブラリが目につきます。勢いにも知名度にもそれほど違いがないようなので両者で迷いますね。簡単に触った感じの違いをそれぞれ見ていきます。
typescript-fsa の action 定義
const login = actionCreator.async< LoginParams, LoginInfo, ErrorResponse >('LOGIN')
のように定義します。これで login.started/done/failed
という非同期処理用の 3 種類の action が定義されます。ペイロードの type narrowing には isType が用いられ User-Defined Type Guards で実現されているみたいです。
typesafe-actions の action 定義
const login = createAsyncAction( 'LOGIN_REQUEST', 'LOGIN_SUCCESS', 'LOGIN_FAILURE' )<LoginParams, LoginInfo, ErrorResponse>();
のように定義します。これで login.request/success/failure
という非同期処理用の 3 種類の action が定義されます。typescript-fsa との違いとして 3 種類のアクション名を定義しなければならないのですが action の数が増えてくると結構面倒です。あるデータモデルのCRUD操作をしようとすると複数GETも含めて 15 個の名前を定義せねばならず、さらにそれがデータモデル分必要になってくるという。
関数なんかで生成しようとしても、現状の TypeScript には String Literal をマッピングするような操作(プレフィックスをつけた新たな String Literal を定義する等)ができず、関数を通すとただの string 型に落ちてしまうという弊害があります。typesafe-actions の type narrowing は String Literal により実現されているようなので、その恩恵にあずかるには全てのアクション名をきちんと定義する必要がありそうです。
action 定義ライブラリについての所感
パッと見の定義の楽さで言えば typescript-fsa を取りたいところですが、そこからさらに redux-thunk を使うか redux-observable を使うかの選択次第で、実は typesafe-actions が使いやすいかもしれないですし、そうでないかもしれません。
さらにこれがより実際的になってくると fetch 対象のデータモデル数が 7-8 種類程度の分量になってきたり、combineReducers
などの階層化が必要になってきたりして、その場合もちゃんと分かりやすく構造化して実装を整理できるのか、そして型定義はどうなるか、そういったこと次第でもまた選択肢が変わってくるかもしれません。
実際に typescript-fsa と typesafe-actions を行ったり来たりした感想ですが、そこまで大規模な書き換えは必要にならなかったので、現在の実装が詰み気味になり対向が気になってきたら書き換える、という方針も取れなくはないかもしれません(不安)。主に書き換えたポイントとしては以下の通りです。
- import するライブラリ
- 非同期用の action の 3 種類のフィールド名
- type narrowing の方法
ただ typescript-fsa-reducers というライブラリを使って reducer を書いていると行き来がしづらくなるので方針が固まるまでは if
で地道に reducer を書くようにとどめておくのがいいかもしれません。
...という、Redux + TypeScript + Side Effect という交差点では、やはりというか組み合わせが爆発しているのだなという、ため息に似た現状振り返りなのでした。この辺はもう、検証しながら正解スタックを探す覚悟を決めねばやっていけません。
といってもそもそも Redux をそこまでまだ理解していないので、まずはそこから、プラクティスをかき集めていきたいです。
...Apollo が懐かしいです。そう、初めは Apollo が提供している機能を「別に Redux でハンドメイドできるんじゃないか」と思って書き始めたのでした。分厚いレイヤーに嫌気がさして Redux に手を伸ばしたわけですが、自分で書くと想像以上に込み入ったコードになってしまい...フレームワークって偉大ですね、という身も蓋もない着地点が視界の端に移り始めてきたのでした。