【個人開発】Nuxt.js,Ruby on Rails, AWSを使ってSPAの手動統合テスト管理ツールを開発した話　①コンポーネント分割について(AtomicDesign)

#完成形
https://itamaster.work
<TABLE ALIGN=CENTER>
  <TR>
    <TD WIDTH=400px>
      <A HREF="https://itamaster.work" TARGET="_blank"><strong>手動統合テスト管理ツール　Itamaster</strong></a>
      <A HREF="https://itamaster.work" TARGET="_blank">
      <IMG BORDER="0" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/4218d71f-684b-d9ae-740c-d49cc42ed790.png" BORDER="0" ALT="" /></a>
      <BR
    </TD>
    <TD WIDTH=150px>
      <IMG STYLE="float:left;margin-right:.5em;" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/1b4fc1dd-875a-bd11-267c-23ed25ac1fa4.png" ALT="" WIDTH="120" HEIGHT="120" />
    </TD>
  </TR>
</TABLE>

#リリース記事リンク
https://qiita.com/Itamaster/items/f821be4c33caab640a93

#関連記事
**フロントエンド**
[①コンポーネント分割の考え方について(AtomicDesign)](https://qiita.com/Itamaster/items/055d7ca05fd873e84a3f)
以下は未公開です。
②プロパティの渡し方とイベントの伝播
③vuetifyとスタイルカタログ(storybook)
④Storeによるデータ管理(vuex)
⑤SPA化について
⑥AxiosとRepositoryFactoryパターン
⑦Axios（JWT認証、インターセプタ）
⑧i18nによる多言語化対応

#使用技術
- npm 7.6.3
- nuxtjs 2.15.3 (SPA モード)
- @nuxtjs/storybook 4.0.2
- vuetify (@nuxtjs/vuetify 1.12.1)
- ruby 2.6.8
- Ruby on Rails 6.1.4 (APIモード)
- EC2(t2.small)
- ALB
- ACM
- RDS (mysql)
- Route53
- S3
- nginx

#Nuxt.jsとは
Nuxt.jsは、Vue.jsベースのJavaScriptのフレームワークです。
コンポーネントベースでUIを開発します。

#Nuxt.jsの選定理由
選定理由としては、以下があります。

- 業務で、LaravelのUIをVue.jsで四か月弱開発したことがあり、心得があったため
- ツールシステムでは画面遷移時の通信の必要の無いSPAを利用したかった。Nuxt.jsならrouterがプロジェクト作成時から付属しており、SPA開発が容易であるため
- 私自身が初学者の域で、開発事例と日本語ドキュメントの豊富なフレームワークを利用したいと考えたため

#モダンフロントフレームワークにおけるコンポーネントとは？
**Webページにおける、UIの部品**です。
例えば、以下はGoogle翻訳の画面なのですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/ab372a42-544c-59d2-68ea-65dad2e55a4e.png)
コンポーネント分割を行うと以下のようになります。（分割粒度は、人による場合も多いと思いますが、イメージの一例として進行します）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/c0e9aa80-4df6-7af5-8aa4-6630a4c96138.png)

①
言語の選択ダイアログを表示します。
言語が選択された時、値をセットします。

②
左右のコンポーネントにセットされている値を入れ替え
③の値を④にセットし、再翻訳します。

③
④にテキストが入力された時、翻訳後のテキストがセットされます。
テキストがセットされている場合、翻訳後のテキストをコピーするためのボタンが表示されます。
（翻訳後のテキストをコピーするためのボタンも、今回のような分け方であれば一つのコンポーネントとなります）

④
翻訳対象のテキストを入力できます。
テキストが入力されている場合、テキストを読み上げるためのボタンが表示されます。

⑤
音声入力を開始します。

⑥
フィードバック送信用のダイアログを表示します。

⑦
翻訳データや、言語データを保持したままGoogle翻訳ページにページ移動します。

コンポーネントから開発する際は、通常、一つのコンポーネント＝１ファイルで開発を進行します。
そして、画面の部品であるコンポーネントをモジュールとしてimportし、配置することで、画面を開発する流れとなります。

#コンポーネント分割を行う目的
以下のようなものかと存じます。
##目的が共通である画面部品を再利用することで、変更に強くする。
上で挙げたGoogle翻訳の画面の、
①のコンポーネントのように、複数箇所で、同じ目的のために利用するUI部品がある場合をイメージしてください。

例えば現在、言語を表す文字は青色で表示されていますが、何らかの理由によって赤色にする事となったとします。

上の例ではたった二箇所ですが、これが十箇所、二十箇所で利用されているUIだとすると、**その分だけ修正**を加える必要があります。
修正箇所が多いことは、**常に人的なミスが発生する可能性があり**、多ければ多いほどその**可能性は高く**なります。
また、**修正コストも大きく**なってしまう、といった問題があります。

このようなUI部品を、一つのコンポーネントに定義し、複数箇所で再利用する事によって、**修正時はたったの一箇所のみを変更するだけで済む**という利点があります。

##画面部品を細かく分割することによって、責任の所在を明確にする
バグやエラー、スタイル崩れの発生時に、症状やエラー文から、どのコンポーネントにバグがあるのか、という事の推測が容易になります。
修正箇所の判別にかかるコストが低減され、保守性が向上します。
同様に、画面部品が細かく分割されていることで、コードの可読性が向上します。

##チーム開発において、同一画面から担当箇所の振り分けが可能になる
チームで開発を行う際、コンポーネントを分割することで、一つの画面を開発する際でも担当を振り分け可能になります。
ランニングタイムの発生を低減出来る他、コンポーネント分割の粒度によっては、スコープを小さくしタスクの管理が可能になります。

##デザインに統一感を出すことが容易になる
新しく画面を開発する際、既存のコンポーネントや、コンポーネントライブラリを使用することで容易に統一感を出すことが出来ます。
cssをファイル毎に設定できるため、コンポーネントに対する使っているCSS、SCSSが一目でわかり、他のコンポーネントのレイアウトを再現する事が簡単になります。

#コンポーネントの分割手法について
コンポーネントの分割において、取られる手法としては**Atomic Design**が多いのではないかと思います。

## AtomicDesign （アトミックデザイン）とは
フロントエンドをコンポーネントベースで開発するフレームワークが主流である現在、最もよく聞くデザイン手法かと存じます。
概要を説明すると、画面上のUIの種類や目的、構造をベースとして、UIオブジェクトを五段階評価するものになります。
以下がAtomicDesignの記事でよく見る画像です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/d53becc7-2402-8162-1605-dd3e17e84c8e.png)

画面上にあるUIオブジェクトを、以下の五つに分割します。

- Atoms（原子）
- Molecules（分子）
- Organisms（有機体）
- Templates（テンプレート）
- Pages（ページ）

それぞれの目的や、開発時の考え方を挙げてみようかと思います。

### Atoms（原子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/0cdc3f90-d619-4c10-e8dc-3b981d6db633.png)
画像のような、「ラベル」「テキスト入力欄」「ボタン」というように、これ以上分けられないような単位のUIオブジェクトを言います。
Atomsを開発する際は、より汎用的に、より多様性を持たせる事を重視するべきかと思います。

例えば、ボタンを開発する際は、ボタンそのものを変更しなくても、色や大きさ、テキストを自由に流し込めるように開発するべきです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/96ff05df-b51e-5bab-1b37-35d053414cda.png)

↑の画像は、Vuetify(vue.js用のデザインライブラリ)のボタンコンポーネントのサンプルです。
コンポーネントに対して、「Large」をオプションとして渡せばボタンサイズが大きくなり、「Small」を渡せばボタンサイズが小さくなります。
Colorを指定すればボタンの背景色が変わりますし、「CLICK ME」と書かれているテキストもオプションとして流し込む事が可能です。
「Disabled」にした場合はボタンが灰色になりClickイベントが無効になり、
「Rounded」にした場合はボタンが丸角になります。

**「青色のボタンが欲しいなぁ」と思った際は、青色のボタンのコンポーネントを作るのではなく、**
**「既存のボタンコンポーネント」に「Color:青」を渡せるようにする、というような形になります。**

Atomsはステートレスです。情報を持ってはいけません。
画面上の最小単位であるため、Atomsには他のコンポーネントを使用してはいけません。

同様に、画面上の最小単位であり、スタイルを持つだけであるべきです。具体的な処理は持つべきではありません。
例えば、ボタンがClickされた際は、Clickされた、というイベントのみを上位のコンポーネントに伝播させるべきです。

###Molecules（分子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/5d2afdfc-33aa-7598-166f-bad626114d85.png)
Atomsコンポーネントを組み合わせて作るコンポーネントです。
例えば画像のMoleculesコンポーネントは、
「"SEARCH THE SITE"と書かれたAtomsのラベルコンポーネント」
「"ENTER KEYWORD"と書かれたAtomsのテキスト入力欄コンポーネント」
「"SEARCH"と書かれた、"橙色"のAtomsのボタンコンポーネント」
から成り立つ、検索フォームです。

この場合、「Moleculesの検索フォーム」の役割は以下になります。

- ラベルコンポーネントを画面上に配置する
- ラベルコンポーネントに"SEARCH THE SITE"というテキストを流し込む
- テキスト入力欄コンポーネントを画面上に配置する
- テキスト入力欄コンポーネントに"ENTER KEYWORD"というテキストを流し込む
- ボタンコンポーネントを画面上に配置する
- ボタンコンポーネントに"SEARCH"というテキストを流し込む

Moleculesはステートレスです。情報を持ってはいけません。
Moleculesで使ってよいのはAtomsのみです。他のMolecules、Organisms等は絶対に使わないようにしましょう。
（下位のコンポーネントが上位のコンポーネントを呼び出す場合、追跡性やバグの原因特定性が著しく低下してしまいます。）

###Organisms（有機体）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/0d708466-0ea4-ddde-a57d-2f0351050be7.png)
Moleculesコンポーネントや、Atomsコンポーネントを組み合わせて作るコンポーネントです。
例えば上の画像のOrganismsは「検索フォーム」やその他「メニュー」や「サイトのロゴ」等が含まれるページヘッダーです。
Organismsの特徴の一つとして、**「使用目的、使用箇所が一意に特定できるもの」**であると言えます。

Moleculesの検索フォームと比較すると、どこに配置されるか、という事が誰でも言語化可能だとわかります。
他にOrganismsとなるようなUIオブジェクトを挙げると、
「ページフッター」、「（何かしらの）データの一覧テーブル」、「（登録系、情報系等の）ダイアログ」等があります。

Organismsはステートレスではありません。情報を持ってもかまいません。
Organismsは例外的に、他のOrganismsを呼び出す事があります。
Template、Pages等上位のコンポーネントを呼び出してはいけません。

基本的にストアや、APIへのアクセスはOrganismsの範疇ではないと考えます。
（こちらはチームによるようです。Organismsからはストアへのアクセスを行っても良しとするチームもあるようです。）

###Templates（テンプレート）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/27a8367b-747e-b7ad-71d8-f22fa6608ea6.png)
Organismsを組み合わせ、ページの枠組みを構成します。
Templatesの役割としては、各コンポーネントを配置し、レイアウトを調整する事のみと考えます。
（イベントや値の伝播も含まれます）
よって、ストアや、APIへのアクセスはTemplatesの範疇ではないと考えます。
（こちらもチームによるようです。）

###Pages（ページ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/04308288-ad8e-9309-9cd3-1a6f3fe7b171.png)

Templateに具体的なコンテンツを入れ込んだのが、**最終的なアウトプットとなるPage**です。
Pageに配置するコンポーネントは、**Templates一つのみ**となると思います。
コンテンツとは、サーバーから取得したデータや、Storeから取得したデータになります。
また、画面遷移や、API通信もpageの役割とする場合もあります。
(Storeがある場合でも、API通信をpageの役割とする事があるようです)

## 実践
あくまで、私のやり方です。
正しいものではないと思いますが、出回っている情報もあまり多くないので記述します。

###ディレクトリ構成
Nuxt.jsには、デフォルトでcomponents,pagesというディレクトリが生成されています。
AtomicDesignを踏襲し、components直下にMolecules,Organisms,Templateを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/a1db49c5-f908-3139-f3f3-6ca6d736bc12.png)

Atomsから開発していない理由は、コンポーネントライブラリを利用したためです。
Vuetifyを使いましたが、デザイン面はほぼ網羅されているため、既存のAtoms(デザイン)に機能やレイアウトを盛り込んでいく形で実装しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/3b52b6c7-6df2-5963-56e8-560bd9b5cc7f.png)

コンポーネントの分割方法としては、AtomicDesignの下に、目的別にディレクトリを作成しました。
共通系のコンポーネントで使うMoleculesコンポーネントを、Commonの下にまとめ、
Project系の画面で使うものをProjectの下にまとめています。
他も同様です。
またこれはOrganisms,Templateも同様のまとめ方をしています。

###命名
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/0b77b342-6828-a278-2f6b-9947aa6cc2f5.png)

何をするものなのか、何のためのコンポーネントなのか、という事を、コンポーネント名を見ただけでわかるように名付けました。

###呼び出したコンポーネントの配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/866d8ad5-455c-8fbd-1644-957d17978990.png)

子コンポーネントとなるものには、自身の位置を書かずに、親コンポーネントで配置をしています。

次回では、コンポーネントへの値の渡し方とイベントバブリングについて記事を書かせていただこうと思います。