Vue.jsのコンポーネント設計に滅茶苦茶悩んだ話

# はじめに
この度Vue.js（2系） + Vuetifyの構成（Laravelを使ったMPA）で社内で初めてVueを導入しての開発を行う事になったのですが、フロントエンドエンジニアが私一人ということもあり、コンポーネント設計で結構悩んだ話です。

そこで一応自分なりの結論が出たので、共有いたします。
まだまだ粗削りな部分はあるかと思いますが、少しでも皆さんの参考になれば幸いです。

# 詳細設計

まずVueに限らずコンポーネント志向のフレームワークでは基本的にAtomic Designをベースに設計すると思います。
そのため初めはそれに則り設計していたのですが、コンポーネントの粒度をどうするかの部分がイマイチ決まらず一度設計を見直す事になりました。

※[そもそもAtomic Designとは](https://www.weblab.co.jp/staff/creator/7352.html#:~:text=%E3%82%A2%E3%83%88%E3%83%9F%E3%83%83%E3%82%AF%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%A8%E3%81%AF&text=%E3%82%A2%E3%83%88%E3%83%9F%E3%83%83%E3%82%AF%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%AF%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E3%81%AE,UI%E3%81%8C%E4%BD%9C%E3%82%89%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82)

そこで、他の設計手法はないかと色々な企業様やエンジニアの方の記事を参考にさせていただきました。

**※参考にさせていただいた記事**
[①Atomic Designが刺さる現場・刺さらない現場](https://logmi.jp/tech/articles/321961)
[②BASEのVue.jsコンポーネントの設計](https://devblog.thebase.in/entry/2019/11/27/110000)
[③ワイ「何でそんな小っさいコンポーネント作ってるん？ｗ](https://qiita.com/Yametaro/items/e8cb39b1a20b762bfafa)

コンポーネント設計の概要については①、③の記事で勉強させていただき、詳しい設計は主に②のBASE様のコンポーネント設計を参考にさせていただきました。

まず上記②を参考にコンポーネントの種類は、

- 唯一http通信とstoreへのアクセスを行え、機能単位でのstateを持つベースとなるコンポーネント（**Base**）
- 表示にのみ責務をもつコンポーネント（**Presentational**）
- 様々なコンポーネントで再利用される、**Presentational**コンポーネント（**CommonPresentational**)

の三種類としました。

5つに分かれているAtomic Designと比べると大分少ないと思います。
ただ根本的な思想の部分は同じで、まず「Pages」と「Templates」に相当するのが**Base**です。
上記の通り機能単位の比較的大きなstateを持ち、唯一http通信とstoreへのアクセスが行えます。

次に「Organisms」と「Molecules」と「Atoms」に相当する、表示にのみ責務を持つのが**Presentational**になります。
その中から再利用するものは**CommonPresentational**に切り出し、あとは「Atoms」や「Molecules」単位で切り出す事を強制せず、必要に応じて新たな**Presentational**を作るか、コンポーネントに直接記述します。

そしてコンポーネントの命名規則ですが、**Base**には頭に`base-`をつけ、**Presentational**は親の**Base**の`base-`以降の文字を受け継ぎます。
**例:**　`base-header` > `header-bar`, `header-drawer`
また**CommonPresentational**は頭に`common-`を付ける事としました。
**例:**　`common-breadcrumbs`

# コード

ではコードの面で具体的にはどうなっているかですが、以下のサンプルページでの例をご覧ください。

**-サンプルページ-**

![sampleapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642041/80585ab0-e437-9c35-d8d1-cb1c80305e8e.png)

まずは全体の構成。
（※本来`base-header`は全ページ共通なので`app.blade.php`に記述し、`base-contact`はルーティングで切り替える部分なので実際の構成とは異なっており、レイアウトやデータの受け渡し部分も分かりやすくするためにあえて端折っています。）

```vue:app
<template>
  <div id="app">
    <base-header />
    <base-contact />
  </div>
</template>
```

次に`base-header`の中身は、

```vue:base-header
<template>
  <header class="header">
    <header-bar />
    <header-drawer />
  </header>
</template>
```

最後に`base-contact`の中身は、

```vue:base-contact
<template>
  <div class="contact">
    <common-breadcumbs />
    <contact-form />
  <div>
</template>
```

のようになっています。

ここで登場するコンポーネントは全部で6つで、画面とともに色分けしたものが以下です。

![sampleapp2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642041/f3b85240-d806-01eb-8e36-2d11de33f406.png)

- Base
    - **<font color="#8147F5">base-header</font>**
    - **<font color="#B4EB46">base-contact</font>**
- Presentational
    - **<font color="#ED4AB7">header-bar</font>**
    - **<font color="#47B6ED">header-drawer</font>**
    - **<font color="#46EB7D">contact-form</font>**
- CommonPresentational
    - **<font color="#F08149">common-breadcrumbs</font>**

※コード量が少なく再利用もしない場合は、更に`contact-form`内をパーツ単位で**Presentational**に切り出すことはせず、コンポーネントに直接記述する。

このような感じで、機能単位でコンポーネントを分けています。

# まとめ
いかがでしたでしょうか。

言ってしまえばAtomic Designの縛りを緩くして、「通信・storeアクセス・state保持用のコンポーネント」・「表示用のコンポーネント」・「表示用の共通コンポーネント」の3つに分けただけなのですが、今のところうまくハマっているのでやり直してよかったです。

それでは。