概要

create-react-app/TypeScript/re-ducksを用いてアプリケーションを作成した雑感

※ 主にre-ducksのことについて書く

コードはこれ(未完)

https://github.com/MasahikoJinno/re-ducks-study


技術スタック


  • TypeScript


    • JavaScriptに型のパワーをもたらすやつ(スーパーセット)

    • コードが大規模になったときに堅牢かつスピーディーに開発できると思う

    • vscodeを使えばめっちゃ補完が効くようになる



  • React



  • Redux



  • create-react-app


    • Reactのビルド環境セットアップツール

    • webpackとかbabelの設定をいい感じにしてくれるやつ

    • SSRは非対応(だったはず)


      • ReactでSSRしたかったらNext.jsがいいと思う



    • 公式ドキュメント



  • re-ducks




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.tsreducers.tsに記述して、副作用をoperations.tsselectors.tsに記述するイメージだと思う。

operations.tsselectors.tsの違いで最初混乱したけど今は以下のように理解している。


  • operations.ts


    • State変更時の(ビジネス)ロジックを記載



  • selectors.ts


    • State取得時の(ビジネス)ロジックを記載




全体的な所感

アプリケーション全体では大まかに以下のようなディレクトリ構成にしている

+ state

└+ ducks
+ views
└+ components
└+ containers
└+ pages
└ App.tsx
index.tsx

大まかにstateviewsで状態管理の世界と表示の世界で分離。

stateには前述のducksディレクトリが入る。

viewsは純粋なComponent(ほぼFunctional Componentsになる)のcomponentsと状態管理の世界と表示の世界を繋ぐcontainersといわゆるContainer Componentsを格納するpagesに分離した。

componentspagesは正直適当。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の依存関係が初めて解消される」ということにようやく気づくことができた。

selectorsoperationsはcontainerが肥大化するのを避けるために生まれた概念なのかなってちょっと思った。


まとめ


  • ViewとStateはお互い依存しないようにしよう

  • 責務分離をしっかりと行おう

  • そのための手段がre-ducks

以上、最後までご覧いただきありがとうございました。