【フロントエンド】最適なコンポーネント設計について考えてみた

## 前置き

　Angularを始めたはいいものの、しばらくして躓くことがありました。それは文法や英語のドキュメントなどよりも、なにより**適切なコンポーネントの分け方がわからない**ということでした。

　そこでいろいろ検索をしてみたのですが、しっくりくる答えにたどり着かなかったため、自分で考えることにしました。今回はその現状報告のようなものです。（コンポーネント設計の話なので、ReactやVue.jsでも通ずる話題かと勝手に思っています）

　結果、Page-Layout-Presenter構造というコンポーネント設計を考えるに至ったのですが、この記事ではそこに至るまでの経緯を紹介し、この設計については別記事で投稿することにしました。ご興味がございましたら併せてご覧ください。
⇒[【Angular】Angularを1年間触ってみてたどり着いたコンポーネント設計: Page-Layout-Presenter構造](https://qiita.com/oioigohan/items/a6b700d67e9fa71ce9f7)

注意：飽くまで**個人的**に**現状**、最もうまくいくと考えている設計なので、プロジェクトや人によってもっと良い方法があるかもしれません！よりよいアイデアがあればコメントをいただければありがたいです。

## これまでAngularを触っていて感じてきた課題

### 思ったよりコンポーネントを分割できない

　コンポーネントの分け方に明確なルールを持ち合わせていないため、感覚で分けるか、そもそも分けないかのどちらかになりがちでした。感覚で分けた場合、複数人で開発していると認識を合わせるのが大変です。そして、開発とともにコンポーネントを分けるコストが高くなり、それによりさらにコンポーネントがfatになるという悪循環で、どんどん動けなくなります。
　このことから、コンポーネントの分け方には明確なルールが必要だと感じました。

### サービスの扱いが雑になりがち

　コンポーネントにビジネスロジックを書いてしまい、サービスとコンポーネントの境界があいまいになってしまうことがありました。また、どのコンポーネントがどのサービスを呼び出しているかを管理できず、親コンポーネントと子コンポーネントで同じサービスを繰り返し呼び出してしまうなどの無駄が発生することもありました。
　このことから、コンポーネントにビジネスロジックを書かないように意識しやすい設計にする必要があると感じました。また、サービスを呼び出せるコンポーネント群を決める必要があると感じました。

### 気軽にスタイルシートを変更できない

　`<div class="some-block__some-element">`のように、スタイルとテンプレートを`class`属性で紐づけることはごく一般的だと思いますが、デザインの修正を行うたびに、`class`を参照して、スタイルシートに移動して、対象のスタイルを探して変更する、というのが私としては意外と手間だと感じました。そして、パーツの内容を考慮した上で、認識のずれが起こりづらいような、一意なクラス名を英語で考えるというのも結構疲れる作業だと思いました。そして、スタイルシートとテンプレートの分離がエンジニアさんとデザイナーさんとの壁になっているのではと思いました。
　また、プロジェクトによってはスタイルがすべて`styles.scss`などグローバルな場所に集められている場合もあり、変更の影響範囲がすぐに把握できないこともありました。私の現職のプロジェクトは実際そうなっていて、専任のエンジニアさんしかスタイルシートを触れないという状態でした。（そしてその方はすでに退職済みという...）
　このことから、設計を考えるうえでデザインないしデザイナーさんとのかかわり方を考えることは切っても切り離せないと感じました。

### コストが高すぎてテストコードを書けない

　Angularの場合、コンポーネントやサービスなどを新規作成すると必ずついてくる`*.spec.ts`というテストファイルがあると思います。テスト駆動開発などではこのファイルにテストコードを書いていくことでテストを極力自動化させるのだと思っています。しかし、実際はやろうと思っても後回しにされることも多いのではないかと推測します。それは、JasmineやKarmaについて学ぶ必要があるという点もあるとは思いますが、なにより、先述のことが原因でコンポーネントがfatになっていて、テストコードの実装の難易度が上がりすぎているのではないかと考えられます。一つのボタンを押すだけでそのコンポーネントでテストすべき項目は一体いくつあるのでしょうか。
　このことから、コンポーネントを適切に分割し、最小限の役割を持たせることで、テストの範囲も分担させ、最小限にすることができるのではないかと思いました。

## 参考にしてきた設計思想

　これら課題を解決すべく、よりよいコンポーネント設計について考えていくことにしました。まずは、そのために参考にした設計思想などを紹介します。

### 【Page-Container-Presentational構造】

知ったきっかけ: [Angular Webアプリケーションの最新設計手法](https://speakerdeck.com/koumatsumot0/angular-webahurikesiyonfalsezui-xin-she-ji-shou-fa)

　紹介されているコンポーネントの分け方から、ここでは仮にPage-Container-Presentational構造と呼ばせていただくことにします。(下画像は上記リンクより引用)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505645/1eaaefa7-b061-a8dd-f4b7-ab5d47c60af5.png)

#### 参考にした点

　関心の分離の観点から、コンポーネントをグルーピングして役割を与えるという考え方が非常に参考になりました。例えば`Container Component`は状態を扱い、`Presentational Component`は見た目を扱うなど。

#### 課題

　課題というか、おそらく私の理解不足が問題なのですが、状態管理以外の点で考えた場合は、`Container`と`Presentational`の境界をどこで決めればよいかがあいまいに感じました。また、最小単位である`Presentational`がfatになることがあるので、共通処理などを抽出し、さらに分割したいと感じました。

### 【Atomic Designとドメイン駆動開発(DDD)】

知ったきっかけ: [WEB+DB PRESS Vol.112](https://gihyo.jp/magazine/wdpress/archive/2019/vol112)
![WEB+DB PRESS Vol.112の表紙](https://gihyo.jp/assets/images/cover/2019/thumb/TH160_9784297107871.jpg)

　この雑誌は発行が2019年と少し前ではありますが、最新設計手法としてAtomic DesignとDDDを両立させたコンポーネント設計について解説されていました。ちなみにAngularではなくReactとVue.jsのコードを例にして書かれていました。

![Atomic Design](https://media.graphcms.com/5rTeh3aQUaArAshdtNWR)
#### 参考にした点

　コンポーネントを、ドメインを担うコンポーネントと、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`の命名ルールを与えることで運用しやすくするという方法です。

![BEM記法](https://laraweb.net/wp-content/uploads/2020/10/img01.png)

#### 参考にした点

　`class`属性値の命名方法にルールがあることで、他のエンジニアさんとの認識のずれを起こしづらくできることが非常にメリットと感じました。
　また、先述のAtomic Design + DDDと併用することで、スタイルのスコープをグローバルではなく上の表でいう`Domain Elements`単位で行えるようになりました。これにより、コンポーネントレベルでスタイルを表現できるようになり、さらに、スタイル修正のコストを減らすことにつながると考えられました。
　そして、スタイルの構造はテンプレートの構造とは関係がない、という考え方は自分にとって重要な学びでした（HTMLにどれだけ階層構造があったとしても、BEMで書くクラスはBlockとElement（Modified Element）との2層構造であるというルール）。これにより、スタイルを`class`値単位で再利用可能にしたというわけです。

#### 課題

　ただ、コンポーネント設計を考えるうえで、再利用性はコンポーネント側がすでに持っているので、もはやスタイル側に持たせる必要がありません。むしろ、異なるコンポーネントで見た目を少しだけ変えたいというような場合、再利用性があることが逆に修正を難しくする原因になりかねません。なので、スタイルの再利用はさせないほうがいいのではないかと考えています。
　そもそもクラス名を介してスタイルを紐づけるというやり方がよくないと思いました。なぜなら、その場合テンプレートからスタイルを参照する際に必ずスタイルシートに移動してさらに`class`名を辿るという手順が発生するためです。これを毎度行うことになるので、やはり少々面倒です。また、`class`名はプロジェクトによって異なるものであり、共通認識のものではないため、ほかのエンジニアさんやデザイナーさんにとっては可読性を下げることになりかねません。また、大規模な場合は認識合わせを行ったり、ドキュメントを作成したりする必要が出てくると思うので、コストが意外と大きいのではないかと思っています。代わりに共通認識の何かで代用できれば、認識のすり合わせもドキュメント作成も不要になるのではないかと考えました。→ tailwindcss
　~~次に、実はコンポーネントを分割するという観点からいうと、BEMは相性がそこまでよくないという気がしています。Angularでは、親コンポーネントからは子コンポーネントのテンプレートを知ることはできません。なので、子コンポーネントのタグの`class`属性も記述することができないため、コンポーネントの分割がそもそも不可能になってしまうからです。~~この点は`angular-bem`というモジュールで解決できます。これについても一苦労あったのですがその話はまたの機会とします。
　BEM記法はコンポーネントを分割しない場合には使いやすいですが、コンポーネント指向な開発にはあっていないと個人的に感じました。

### 【Windowsフォームアプリケーション】

知るきっかけ: 現職での仕事

![Windows フォームとToolBox](https://docs.microsoft.com/ja-jp/visualstudio/ide/media/csharp-add-button-form1.png?view=vs-2019)

#### 参考にした点

　.NET FrameworkにWindowsフォームというものがあります。簡単に言えば一世代前の、Windows用アプリケーションを作るためのフレームワークです。Windowsフォームでアプリケーションを作成するときは、Visual StudioのToolBoxペインから、必要なコントローラ(UIパーツのようなもの)をドラッグ&ドロップすることで配置することができます。つまり、必要なUIのパーツはすでにフレームワークによって用意されていて、開発者はマウス操作一つで直感的にそれらを配置することができるわけです。
　Webアプリケーション開発でも、このように、より直感的にUIパーツを配置できれば楽なのにな、と考えるようになりました。


#### 課題

　Angularやnpmによって、予めすべてのUIパーツが準備されていれば楽ですが、現状はそうはなっていません。また、Windowsフォームアプリケーションはスタイルのカスタマイズ性が低く、スタイルをカスタマイズする場合についても考える必要があります。

### 【tailwindcss】

知ったきっかけ: [The State of CSS 2020から読み解くWebフロントのトレンド](https://www.youtube.com/watch?v=HBSSXmUjBqY&t=73s)

![tailwindcss](https://tailwindcss.com/_next/static/media/tailwindcss-logotype.128b6e12eb85d013bc9f80a917f57efe.svg)

#### 参考にした考え方

　BEM記法はAngularで実用するには無理があると感じていたところ、tailwindを知りました。テンプレートに直接スタイルを記述できるため、頻繁に変更されると考えられるレイアウトに関するスタイルなどを効率よく操作でき、さらに、`class`名の運用に関する他のエンジニアさんとの認識のずれをなくせると考えられました。あと、ダークモードやレスポンシブ対応など向けの実装も簡単そうなのでいいと思いました。

#### 課題

　現状、tailwindcssが`class`として提供できていないスタイルに関しては`style`属性か結局スタイルシートに書く必要があります。また、これはBEM記法でも同じ課題ですが、少しだけ見た目の違う場合に新しいコンポーネントとして分けるべきか悩ましく、まだ私の中で最善策が決まっていません。


### 【ディレクトリ構造】

[中規模AngularアプリにおけるNgModule構成とディレクトリ構造](https://blog.lacolaco.net/2019/07/angular-ngmodule-and-directories/)

#### 参考にした点

　アプリケーション全体で参照可能な共通のディレクトリと、同一`feature`内でのみ参照可能な`features`ディレクトリを作るという点が参考になりました。また、ディレクトリ名を何にするか悩んだ時にいくつか参考にしました。

### 【その他参考にした記事など】

- [Angular Architecture Patterns and Best Practices (that help to scale)](https://dev-academy.com/angular-architecture-best-practices/)
- [angular-atomic-design-boilerplate - StackBlitz](https://stackblitz.com/github/sergio9104/angular-atomic-design-boilerplate?file=src%2Fapp%2Fapp.component.html)
- [中規模Angularアプリケーションの再設計](https://www.slideshare.net/bitbankink/angular-159706933)
- [bitbank LT Night #3 ~Angular~を開催しました](https://tech.bitbank.cc/bitbank-lt-night-3-angular/)
- [Atomic Designを使ってReactコンポーネントを再設計した話](https://blog.spacemarket.com/code/atomic-design%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6react%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E5%86%8D%E8%A8%AD%E8%A8%88%E3%81%97%E3%81%9F%E8%A9%B1/)

## 個人的に最適と思うコンポーネント設計

以上を踏まえ、できる限り課題を解決するような設計について私なりに考えてみました。
これについては別記事で投稿しましたので、よければ併せてご覧ください。
⇒[【Angular】Angularを1年間触ってみてたどり着いたコンポーネント設計: Page-Layout-Presenter構造](https://qiita.com/oioigohan/items/a6b700d67e9fa71ce9f7)

## おわりに

今後もフロントエンド開発に関して学びつつ、よりよい設計について考えていきたいと思います。
記事として未熟な点もあったかもしれませんが、一部でも参考になれば幸いです。