概要
create-react-app/TypeScript/re-ducksを用いてアプリケーションを作成した雑感
※ 主にre-ducksのことについて書く
コードはこれ(未完)
https://github.com/MasahikoJinno/re-ducks-study
技術スタック
- TypeScript
- JavaScriptに型のパワーをもたらすやつ(スーパーセット)
- コードが大規模になったときに堅牢かつスピーディーに開発できると思う
- vscodeを使えばめっちゃ補完が効くようになる
- React
- JavaScriptのViewライブラリ
- 公式ドキュメント
- Redux
- JavaScriptの状態管理フレームワーク
- 公式ドキュメント
- create-react-app
- re-ducks
- Reduxで秩序あるコードを書くためのプラクティス
- ディレクトリ構成とファイルごとの責務分離を定めたデザインパターン(だと思ってる)
この名前めんどくさくないっすか?もうちょっとReduxの発音と変えてほしい。- 参考
TypeScriptについて
乗るしかねぇッッ!!
このビッグウェーブにッッ!!
色々めんどくさいところもあるけど、VSCodeによる補完は素晴らしい。
基本プロとして複数人でコード書くならTypeScriptにしたほうがいいと思う。
使い捨てだったり、(未来永劫)自分一人で書くコードだったり、短命(なのが確実)なアプリだったらJavaScriptでもいいかな・・・
create-react-appについて
webpackとかbabelとかあんまり考えずにReactのアプリ作れるのでとても便利だと思う。
そのかわりwebpackの設定とか書き換えられないけどそのへんはお察しください。
というかSSRをしないという前提なら、create-react-appのwebpack書き換えたいって思った時点でちょっとアーキテクチャが正しいのか考え直したほうがいい。基本的にセオリーからズレると思うので。トリッキーは辛いぞ。
クライアントサイドレンダリングのだけのSPAを作るならcreate-react-app一択かなぁと思ってます。
思考停止してモダンな環境作れるので。
re-ducksについて
今まで、以下のようなディレクトリ構成でReduxを用いていた。
https://github.com/MasahikoJinno/study/tree/master/js/react_todoapp
- components: Functional Componentを配置
- containers: Container Componentを配置
- actions: ReduxのAction Creatorsを配置
- reducers: ReduxのReducerを配置
同じ名前のファイルが大量にできるし、Stateの世界とViewの世界がごっちゃになってる感があってあまりしっくり来ていなかった。
re-ducksというものを知り、そのデザインパターンがどのようなものなのか知るためにサンプルコードを書いてみた。
俺の思うre-ducksでの責務分離
re-ducksは以下のようなディレクトリ構成になる
+ ducks
└+ todos
└ actions.ts
└ index.ts
└ operations.ts
└ reducers.ts
└ selectors.ts
└ types.ts
各ファイルの責務分離は以下のようになっている。(と思う)
※ index.tsはただのモジュールのエントリポイントだから無視。
※ 詳しくは冒頭のリポジトリを見てほしい。
actions.ts
- Action Creatorを配置する
- Action Creatorは単純なオブジェクト(ActionTypeとPayload)を返すだけにする
- Dispatchとかしない
- 値の計算もしない
- async/awaitとかも書かない
operations.ts
- Actionを発火するためのラッパー関数を配置する
- Actionを複数かい同時に叩きたい場合やThunkを使う場合はここに書く
- Actionにわたす引数の作成等もこのoperationsで行う
- Actionを発火させたい場合は、必ずこのoperations経由で行う
- Actionをカプセル化する
- ViewはどのActionを発火するとか知らないほうがいい
reducers.ts
- ここは他のパターンでもあんまり変わらないと思う
- Reduxのreducerを配置する
- Stateの定義とActionTypeに応じたState変更処理を記述する
- 書いてて思ったけどファイル名複数形じゃないほうがいいね
selectors.ts
- Viewが使いたい値はStateの値とイコールとは限らない
- Viewが使いたい形にStateを加工する関数を配置する
- 単純なデータ構造であればStateをそのまま利用しても良いが、ViewがStateを知りすぎるのは良くないのでViewが使いやすい形に加工して渡してやるのがよい
- 配列のフィルタリングとかはここでやる(Viewにロジックを書かない。ただ渡されたものを表示するだけにする。)
types.ts
- ActionTypesの定義とかはここでしている
- actions.tsで定義すればよくね?って意見もあるけどそれでもいいと思う(好みの問題)
- TSじゃなかったら不要かなぁ
Reduxの作用はactions.ts
とreducers.ts
に記述して、副作用をoperations.ts
とselectors.ts
に記述するイメージだと思う。
operations.ts
とselectors.ts
の違いで最初混乱したけど今は以下のように理解している。
- operations.ts
- State変更時の(ビジネス)ロジックを記載
- selectors.ts
- State取得時の(ビジネス)ロジックを記載
全体的な所感
アプリケーション全体では大まかに以下のようなディレクトリ構成にしている
+ state
└+ ducks
+ views
└+ components
└+ containers
└+ pages
└ App.tsx
index.tsx
大まかにstate
とviews
で状態管理の世界と表示の世界で分離。
stateには前述のducks
ディレクトリが入る。
views
は純粋なComponent(ほぼFunctional Componentsになる)のcomponents
と状態管理の世界と表示の世界を繋ぐcontainers
といわゆるContainer Componentsを格納するpages
に分離した。
components
とpages
は正直適当。Atomicデザインとかに習えばatom
, molecules
, organisms
, templates
, pages
とかになるのかな・・・
ただ、containers
とその他は分けたほうがいいと思う。(containersにJSX書かないほうがいい)
んで、何が嬉しいの?
究極は「責務が明確になる」であると思う。
re-ducksのファイル分割では、各々の責務が明確になっており、actionsやreducerは非常に単純なコード(ピュアな状態)を維持できる。
ユニットテストも容易になると思われる。
その分operationsとselectorsに副作用が追いやられているが、これはこれでどこのテストをしっかり書かなきゃだめかわかりやすくなると思う。
re-ducksとは直接関係ないけど、今回コードを書いていてViewとStateの依存をなくすことの重要さを再認識した。
Stateのデータ構造がViewのに依存する形で作られていたり、ViewがStateの構造を完全に理解して実装されていたりっていうのはあるあるだと思うがこの状態は良くない。
ViewはViewとして必要なものだけを、StateはStateとして必要なものだけを実装すべきだと思うし、そうでなければ作業分担当もできない。
ViewとStateの互いの依存をなくすために、container
の実装が重要になってくる。
「container
がViewとState両方に依存して、複雑さを一手に引き受けることでViewとStateの依存関係が初めて解消される」ということにようやく気づくことができた。
selectors
とoperations
はcontainerが肥大化するのを避けるために生まれた概念なのかなってちょっと思った。
まとめ
- ViewとStateはお互い依存しないようにしよう
- 責務分離をしっかりと行おう
- そのための手段がre-ducks
以上、最後までご覧いただきありがとうございました。