コネヒト開発者ブログ

コネヒト開発者ブログ

JavaScript(React+Flux)のディレクトリ構成がガタガタになりそうだったので反省して改善する

f:id:dachi023:20170524174143p:plain

こんにちは。フロントエンジニアの@ry0_adachiです。

ここのところ、React + Flux Utils *1 の構成を選択することが多く、それ以外を選択することが少なくなってきました。特に大きな不満はないし、慣れてきたので良いかもな〜と思っていたのですが、開発期間が伸びてきて、アプリ自体の規模がそこそこになってくるとFacebook公式のサンプルコードのようにシンプルな構成だけではカバーしきれない状況が生まれることもあります。運用し始めてだんだんとその辺の辛さみたいなものが見えてきたのと、継ぎ接ぎな構成になるのは嫌だったので、このタイミングで1回整理して改善してみようと思いました。

Fluxについて

Facebook提唱のアーキテクチャ、またはフレームワークです。データの流れを一方向にして、シンプルに状態管理が行えることがメリットとしてよく挙げられています。Fluxについてより詳しく知りたい方は以前書いた記事があるので良ければそちらも合わせて読んでみてください。

f:id:dachi023:20170523163305p:plain GitHub - facebook/flux: Application Architecture for Building User Interfaces

本題に入る前に

GitHubで参考になりそうなFlux Utils製のアプリケーションのディレクトリやファイルの構成を見ていると各プロジェクトに最適化されている(しようとしている)ものが多く、参考になる部分とならない部分があります。この辺はフレームワーク側で制限しない限り、それぞれが使いやすいものが生まれ続けるでしょう *2 。この記事の内容についてもハマるものもあるだろうしハマらないものもあるはずなので、こうした方が良い!というわけではなくて、あくまで参考程度に捉えてもらえると幸いです。

最初に考えた構成

最初に作り始めた時は公式のサンプルコードにあったチャットアプリをベースにして、そのあと最低限必要なものだけを足して下記の図のような構成にしました。

チャットアプリ *3 : flux/examples/flux-chat at 3.1.0 · facebook/flux · GitHub

js-root/
  ├ actions/
  │   ├ api/
  │   │   └ TodoApi.js
  │   └ TodoActions.js
  │
  ├ components/
  │   ├ TodoList.js
  │   └ TodoListItem.js
  │
  ├ constants/
  │   └ ActionTypes.js
  │
  ├ containers/
  │   └ AppContainer.js
  │
  ├ dispatcher/    
  │   └ AppDispatcher.js
  │
  ├ stores/
  │   ├ TodoStore.js
  │   └ records/
  │       └ Todo.js
  │
  └ utils/
  • 補足1. CSSは別ファイルに出力、別で読み込んでいるのでこの図では考慮しない
  • 補足2. ActionCreatorにAPIコールの責務を持たせるのでactions/api/を作成する
  • 補足3. APIクライアント以外で各レイヤーに収まらないものはutils/に集約する

この構成にして反省したこと

役割を階層で表現できていると思っていた

actions/api/が悪い例で、下記が理由です。

  1. ActionCreator以外からは呼んではいけません、を階層で表現しようとしていた
  2. 呼んではいけなさそうだな!という雰囲気を醸しているだけで呼ぼうと思えば呼べる

ガチガチにやるなら、チェックツールなどで「Action以外がAPIクライアントのメソッドをコールしているか」といった処理をするべきで、わざわざディレクトリの構造を複雑にしてまでやることではありませんでした。今は普通にコーディング規約として文字に起こしてチームメンバーに共有しておく、くらいで良いかなと思います。

Componentの中でのレイアウトとコンポーネントの切り分け

Componentの種類を「パーツを組み合わせたもの」と「パーツ」の2つではっきり分けるべきでした。参考にしていた公式のサンプルコードにはMessageSection.react.jsThreadSection.react.jsがComponentとして用意されていて、こういう風にすれば良かったのかと後から気づきました。

共通で使えるComponentなのかどうかが分からない

components/に置かれたものが「プロジェクト全体で共通的に使えるもの」なのか「特定の画面のみで使われるもの」なのかの判断をぱっと見で付けられなくなってしまったのでもう1つディレクトリを切ったほうがよかったです。

より実用的なディレクトリ構成を考える

反省点を改善できるように修正をしたのが下記の図になります。

js-root/
  ├ actions/
  │   └ TodoActions.js
  │
  ├ components/
  │   ├ common/
  │   │   ├ TextField.js
  │   │   └ Button.js
  │   └ todo/
  │       ├ Section.js
  │       ├ TodoList.js
  │       └ TodoListItem.js
  │
  ├ constants/
  │   └ ActionTypes.js
  │
  ├ containers/
  │   └ AppContainer.js
  │
  ├ dispatcher/    
  │   └ AppDispatcher.js
  │
  ├ stores/
  │   ├ TodoStore.js
  │   └ records/
  │       └ Todo.js
  │
  └ utils/
      └ api/
          └ TodoApi.js

改善されたこと

actionsの中はActionCreatorだけになった

actions/api/utils/api/にしたのでactions/にはActionCreatorだけが入ることになりました。API関連はActionCreatorから切り出されたような形になったので、例えばAPIクライアントを別パッケージにします〜となった場合でも切り出すイメージが湧きやすいかもなぁ、とそんなことも思いつきました。

ComponentにSectionを設ける

Sectionを設けたことでレイアウトやパーツの集合体を定義するクラスが明確になり迷うことがなくなりました。Sectionがレイアウトやパーツの集合体でそれ以外はただのパーツである、という認識をしっかり持てたことで前に比べるとチーム開発もしやすくなったかもしれません(今は私1人でJS書いてるのでまだ試してませんが…)。

共通なComponentとそうでないものはディレクトリを分ける

プロジェクト内で使い回しの効くComponentを切り出したことで「前にこんなの作ったけ?」と考える時間が減ったのと、共通化しようという意図が見えやすくなりました。この辺はUIを考えてくれているデザイナーさんに相談しながら少しずつ共通化して切り出していくようにしています。

余談ですが、Componentが共通化される条件は「ロジック的に使い回せるか?」よりも「UIとして使い回せるか?」を判断軸にしています。これは、CSSとComponentの組み方の乖離 *4 が大きくなると実装が後々大変になるだろう、と判断してそうしています。

まとめ

事前に想定できるケースを洗い出しておくのは前提として、最初に決めた構成のまま突き進むのではなく状況に合わせて適宜変更していく・変更しやすい状態を保つことが重要です。また、途中で開発チームの規模が大きくなるかもしれないので複数人でも開発しやすい状態などを考慮しておくことも重要です。開発者が考える時間を減らしてあげることは開発効率の向上にも繋がるのでしっかりとメンテナンスしていきたいですね。

脚注

*1:Flux UtilsはFluxのリポジトリ(facebook/flux)に含まれています。

*2:現状は決定版となる構成が特にない状況だと認識しています。

*3:2017/05/25: masterにはなかったので3.1.0のタグから引っ張ってきています。

*4:CSSは共通化しているのにComponentはされていない、といったズレが生じている状態。