前置き
Angularを始めたはいいものの、しばらくして躓くことがありました。それは文法や英語のドキュメントなどよりも、なにより適切なコンポーネントの分け方がわからないということでした。
そこでいろいろ検索をしてみたのですが、しっくりくる答えにたどり着かなかったため、自分で考えることにしました。今回はその現状報告のようなものです。(コンポーネント設計の話なので、ReactやVue.jsでも通ずる話題かと勝手に思っています)
結果、Page-Layout-Presenter構造というコンポーネント設計を考えるに至ったのですが、この記事ではそこに至るまでの経緯を紹介し、この設計については別記事で投稿することにしました。ご興味がございましたら併せてご覧ください。
⇒【Angular】Angularを1年間触ってみてたどり着いたコンポーネント設計: Page-Layout-Presenter構造
注意:飽くまで個人的に現状、最もうまくいくと考えている設計なので、プロジェクトや人によってもっと良い方法があるかもしれません!よりよいアイデアがあればコメントをいただければありがたいです。
これまでAngularを触っていて感じてきた課題
思ったよりコンポーネントを分割できない
コンポーネントの分け方に明確なルールを持ち合わせていないため、感覚で分けるか、そもそも分けないかのどちらかになりがちでした。感覚で分けた場合、複数人で開発していると認識を合わせるのが大変です。そして、開発とともにコンポーネントを分けるコストが高くなり、それによりさらにコンポーネントがfatになるという悪循環で、どんどん動けなくなります。
このことから、コンポーネントの分け方には明確なルールが必要だと感じました。
サービスの扱いが雑になりがち
コンポーネントにビジネスロジックを書いてしまい、サービスとコンポーネントの境界があいまいになってしまうことがありました。また、どのコンポーネントがどのサービスを呼び出しているかを管理できず、親コンポーネントと子コンポーネントで同じサービスを繰り返し呼び出してしまうなどの無駄が発生することもありました。
このことから、コンポーネントにビジネスロジックを書かないように意識しやすい設計にする必要があると感じました。また、サービスを呼び出せるコンポーネント群を決める必要があると感じました。
気軽にスタイルシートを変更できない
<div class="some-block__some-element">
のように、スタイルとテンプレートをclass
属性で紐づけることはごく一般的だと思いますが、デザインの修正を行うたびに、class
を参照して、スタイルシートに移動して、対象のスタイルを探して変更する、というのが私としては意外と手間だと感じました。そして、パーツの内容を考慮した上で、認識のずれが起こりづらいような、一意なクラス名を英語で考えるというのも結構疲れる作業だと思いました。そして、スタイルシートとテンプレートの分離がエンジニアさんとデザイナーさんとの壁になっているのではと思いました。
また、プロジェクトによってはスタイルがすべてstyles.scss
などグローバルな場所に集められている場合もあり、変更の影響範囲がすぐに把握できないこともありました。私の現職のプロジェクトは実際そうなっていて、専任のエンジニアさんしかスタイルシートを触れないという状態でした。(そしてその方はすでに退職済みという...)
このことから、設計を考えるうえでデザインないしデザイナーさんとのかかわり方を考えることは切っても切り離せないと感じました。
コストが高すぎてテストコードを書けない
Angularの場合、コンポーネントやサービスなどを新規作成すると必ずついてくる*.spec.ts
というテストファイルがあると思います。テスト駆動開発などではこのファイルにテストコードを書いていくことでテストを極力自動化させるのだと思っています。しかし、実際はやろうと思っても後回しにされることも多いのではないかと推測します。それは、JasmineやKarmaについて学ぶ必要があるという点もあるとは思いますが、なにより、先述のことが原因でコンポーネントがfatになっていて、テストコードの実装の難易度が上がりすぎているのではないかと考えられます。一つのボタンを押すだけでそのコンポーネントでテストすべき項目は一体いくつあるのでしょうか。
このことから、コンポーネントを適切に分割し、最小限の役割を持たせることで、テストの範囲も分担させ、最小限にすることができるのではないかと思いました。
参考にしてきた設計思想
これら課題を解決すべく、よりよいコンポーネント設計について考えていくことにしました。まずは、そのために参考にした設計思想などを紹介します。
【Page-Container-Presentational構造】
知ったきっかけ: Angular Webアプリケーションの最新設計手法
紹介されているコンポーネントの分け方から、ここでは仮にPage-Container-Presentational構造と呼ばせていただくことにします。(下画像は上記リンクより引用)
参考にした点
関心の分離の観点から、コンポーネントをグルーピングして役割を与えるという考え方が非常に参考になりました。例えばContainer Component
は状態を扱い、Presentational Component
は見た目を扱うなど。
課題
課題というか、おそらく私の理解不足が問題なのですが、状態管理以外の点で考えた場合は、Container
とPresentational
の境界をどこで決めればよいかがあいまいに感じました。また、最小単位であるPresentational
がfatになることがあるので、共通処理などを抽出し、さらに分割したいと感じました。
【Atomic Designとドメイン駆動開発(DDD)】
知ったきっかけ: WEB+DB PRESS Vol.112
この雑誌は発行が2019年と少し前ではありますが、最新設計手法としてAtomic DesignとDDDを両立させたコンポーネント設計について解説されていました。ちなみにAngularではなくReactとVue.jsのコードを例にして書かれていました。
参考にした点
コンポーネントを、ドメインを担うコンポーネントと、UIを担うコンポーネントに分ける考え方が参考になりました。これにより、特定のコンポーネントからしか参照しないドメイン依存なコンポーネントと、それに対し、不特定多数のコンポーネントから呼び出せるUIパーツなどの共通のコンポーネントを作れるため、再利用性が上がると考えられました。
また、デザインの修正をページ単位よりも細かいコンポーネント単位で行えるため、ページ単位で修正を行うのに比べ、エンジニアがデザイナーを待つ時間とデザイナーがエンジニアを待つ時間が短縮されると雑誌で紹介されていました。これにより開発のサイクルがより小さく早くなります。デザイナーさんとの関わりを考慮した設計を考えるきっかけになり大変参考になりました。
課題
Atomic Designの考えを取り入れると計5層の構造になり、さらに雑誌の手法にあるContainers
層とLayout
層を加える場合は7層以上になります。1つのページを作るだけでもコンポーネントの数が6つ以上必要で作るのが大変です。ほぼ記述がないコンポーネントをいくつも生成することになるので、フロントエンドのコンポーネント設計においては少し冗長に感じました。
層 | Atomic Design + DDD | Atomic Design |
---|---|---|
1 | Pages | Pages(ページ) |
2 | Containers | |
3 | Domain Objects | Templates(テンプレート) |
4 | Domain Elements | Organisms(有機体) |
5 | Gui Groups | Molecules(分子) |
6 | Gui Parts | Atoms(原子) |
- | 必要な場所にLayout |
【BEM記法】
HTMLやCSSについて勉強しているとよく登場します。SASS記法と合わせてよく紹介されている印象があります。ざっくり言うと、自由度が高いHTMLのタグのclass
属性の命名に、block__element--modifire
の命名ルールを与えることで運用しやすくするという方法です。
参考にした点
class
属性値の命名方法にルールがあることで、他のエンジニアさんとの認識のずれを起こしづらくできることが非常にメリットと感じました。
また、先述のAtomic Design + DDDと併用することで、スタイルのスコープをグローバルではなく上の表でいうDomain Elements
単位で行えるようになりました。これにより、コンポーネントレベルでスタイルを表現できるようになり、さらに、スタイル修正のコストを減らすことにつながると考えられました。
そして、スタイルの構造はテンプレートの構造とは関係がない、という考え方は自分にとって重要な学びでした(HTMLにどれだけ階層構造があったとしても、BEMで書くクラスはBlockとElement(Modified Element)との2層構造であるというルール)。これにより、スタイルをclass
値単位で再利用可能にしたというわけです。
課題
ただ、コンポーネント設計を考えるうえで、再利用性はコンポーネント側がすでに持っているので、もはやスタイル側に持たせる必要がありません。むしろ、異なるコンポーネントで見た目を少しだけ変えたいというような場合、再利用性があることが逆に修正を難しくする原因になりかねません。なので、スタイルの再利用はさせないほうがいいのではないかと考えています。
そもそもクラス名を介してスタイルを紐づけるというやり方がよくないと思いました。なぜなら、その場合テンプレートからスタイルを参照する際に必ずスタイルシートに移動してさらにclass
名を辿るという手順が発生するためです。これを毎度行うことになるので、やはり少々面倒です。また、class
名はプロジェクトによって異なるものであり、共通認識のものではないため、ほかのエンジニアさんやデザイナーさんにとっては可読性を下げることになりかねません。また、大規模な場合は認識合わせを行ったり、ドキュメントを作成したりする必要が出てくると思うので、コストが意外と大きいのではないかと思っています。代わりに共通認識の何かで代用できれば、認識のすり合わせもドキュメント作成も不要になるのではないかと考えました。→ tailwindcss
~~次に、実はコンポーネントを分割するという観点からいうと、BEMは相性がそこまでよくないという気がしています。Angularでは、親コンポーネントからは子コンポーネントのテンプレートを知ることはできません。なので、子コンポーネントのタグのclass
属性も記述することができないため、コンポーネントの分割がそもそも不可能になってしまうからです。~~この点はangular-bem
というモジュールで解決できます。これについても一苦労あったのですがその話はまたの機会とします。
BEM記法はコンポーネントを分割しない場合には使いやすいですが、コンポーネント指向な開発にはあっていないと個人的に感じました。
【Windowsフォームアプリケーション】
知るきっかけ: 現職での仕事
参考にした点
.NET FrameworkにWindowsフォームというものがあります。簡単に言えば一世代前の、Windows用アプリケーションを作るためのフレームワークです。Windowsフォームでアプリケーションを作成するときは、Visual StudioのToolBoxペインから、必要なコントローラ(UIパーツのようなもの)をドラッグ&ドロップすることで配置することができます。つまり、必要なUIのパーツはすでにフレームワークによって用意されていて、開発者はマウス操作一つで直感的にそれらを配置することができるわけです。
Webアプリケーション開発でも、このように、より直感的にUIパーツを配置できれば楽なのにな、と考えるようになりました。
課題
Angularやnpmによって、予めすべてのUIパーツが準備されていれば楽ですが、現状はそうはなっていません。また、Windowsフォームアプリケーションはスタイルのカスタマイズ性が低く、スタイルをカスタマイズする場合についても考える必要があります。
【tailwindcss】
知ったきっかけ: The State of CSS 2020から読み解くWebフロントのトレンド
参考にした考え方
BEM記法はAngularで実用するには無理があると感じていたところ、tailwindを知りました。テンプレートに直接スタイルを記述できるため、頻繁に変更されると考えられるレイアウトに関するスタイルなどを効率よく操作でき、さらに、class
名の運用に関する他のエンジニアさんとの認識のずれをなくせると考えられました。あと、ダークモードやレスポンシブ対応など向けの実装も簡単そうなのでいいと思いました。
課題
現状、tailwindcssがclass
として提供できていないスタイルに関してはstyle
属性か結局スタイルシートに書く必要があります。また、これはBEM記法でも同じ課題ですが、少しだけ見た目の違う場合に新しいコンポーネントとして分けるべきか悩ましく、まだ私の中で最善策が決まっていません。
【ディレクトリ構造】
中規模AngularアプリにおけるNgModule構成とディレクトリ構造
参考にした点
アプリケーション全体で参照可能な共通のディレクトリと、同一feature
内でのみ参照可能なfeatures
ディレクトリを作るという点が参考になりました。また、ディレクトリ名を何にするか悩んだ時にいくつか参考にしました。
【その他参考にした記事など】
- Angular Architecture Patterns and Best Practices (that help to scale)
- angular-atomic-design-boilerplate - StackBlitz
- 中規模Angularアプリケーションの再設計
- bitbank LT Night #3 ~Angular~を開催しました
- Atomic Designを使ってReactコンポーネントを再設計した話
個人的に最適と思うコンポーネント設計
以上を踏まえ、できる限り課題を解決するような設計について私なりに考えてみました。
これについては別記事で投稿しましたので、よければ併せてご覧ください。
⇒【Angular】Angularを1年間触ってみてたどり着いたコンポーネント設計: Page-Layout-Presenter構造
おわりに
今後もフロントエンド開発に関して学びつつ、よりよい設計について考えていきたいと思います。
記事として未熟な点もあったかもしれませんが、一部でも参考になれば幸いです。