Vue3のViewComponent構成 (3層構造) / 社内事例

おはようございます。いつもは[wordrabbit](https://wordrabbit.jp/)の福澤としてブログを書いていますが、今日は普段お世話になっている株式会社メタップスホールディングス様にお邪魔して、メタップスホールディングスの福澤としてブログを投稿させていただきます。この記事は Metaps Advent Calendar 2023 18日目の記事です。それではよろしくお願いいたします。

# メタップスホールディングスのフロントエンド技術

メタップスホールディングスでは、会社としての幅を広げたいという考えから、フロントを特定のフレームワークに限定することなく、Reactであったり、Vueであったり、プロジェクトによってそれぞれ別のフレームワークを使っています。

今日ご紹介するサービスの構成は、まだリリースされていないサービスの構成です。この新サービスはVue3なので、すこしVueっぽさは出てきますが、もちろんReactにも同様の観点でコンポーネントを切ることができます。

# 一般的なViewComponentの切り方

さて、ViewのComponentをどのように切るかというのは、なかなか議論が尽きないテーマです。さっと思いつくだけでも一般的に以下のような切り方があります。

**1. AtomicDesignパターン**
粒度単位で切るので再利用性が強く意識されます。

**2. Container/Presentationalパターン**
APIコールなどのデータ形成が分離されているので、コードの見通しがよいです。

**3. Featureパターン**
プロジェクト構造が直感的に理解できます。

新サービスで採用しているコンポーネント構成は、これらのメリットを複合して考えたものです。

# 新サービスで採用しているViewComponentの切り方

新サービスで採用しているパターンは、3層構造をとっています。「Base / Domain / Page」の3層に分かれます。

この3層は呼び方の違いはあれど、近年一般的になってきているように感じます。ナレッジワークさんも同様にこの3層として捉えておられます。(ui/model/page)

https://zenn.dev/knowledgework/articles/99f8047555f700

それぞれの役割を説明します。

## Base

Baseは、UserやProjectといった特定のモデルを持たないコンポーネントです。Button, TextField, Dropdown, Table, Modalなど、UIの基本を作るものをここに配置します。

## Domain

Domainは、特定の文脈で使うコンポーネントです。UserやProjectといったモデルをpropsに取るものもありますし、特定のページでしか使わない説明文などのコンポーネントもここに配置します。

## Page

Pageは、APIからデータをもらい、それらをBaseとDomainに渡す役割を果たすコンポーネントです。Container/PresentationalパターンのContainerに相当します。ここに文章やDOMやCSSは発生しません。APIとBaseとDomainを呼び出すだけです。

# 実際の配置

では、実際にそれらの概念がどのようにディレクトリで表現しているかを示します。

例えば、以下の3つのページがあるとします。

```
/ =>トップページ
/users/ => ユーザー一覧
/users/new => ユーザー作成
```

この場合、以下のような構成になります。

```
src/
  ├ assets
  │    └ base_components
  |       ├ BaseButton.vue
  |       └ BaseFormTextfield.vue
  │
  └ views
       ├ presentationals
       |  |
       |  ├ home
       |  |  ├ TitleDescription.vue
       |  |  └ Dashboard.vue
       |  |
       |  └ users
       |     |
       |     ├ common
       |     |  └ TabNavi.vue
       |     |
       |     ├ index
       |     |  ├ TitleDescription.vue
       |     |  └ IndexUsers.vue
       |     |
       |     └ new
       |        ├ TitleDescription.vue
       |        └ FormUser.vue
       |
       └ containers
          ├ home.vue
          └ users
             ├ index.vue
             └ new.vue 
```

`assets/base_components` を Base
`presentationals` を Domain
`containers` を Page

として捉えて、コンポーネントを配置していきます。

# Page (src/views/containers) の説明

ここは、ルーティングの着地地点です。Containerを見れば、そのページが、どのAPIと接続していて、どんなデータが使われているかを把握できます。近年、Containerを使わずに、末端のComponentからAPI接続するように勧めるケースもあるのですが、大規模になってくると、APIコールの重複が起きたり、データの管理が把握できなくなったりすることがあるため、Containerという概念はまだ残しています。

# Domain (src/views/presentationals) の説明

どの画面からでも使えるコンポーネント以外は、ここに所属します。ポイントは、できるかぎりURLと同じ構造でディレクトリを切っていることです。URLに従うことで、機能単位に近いディレクトリ構成になっています。

URLに従った一番の狙いは、そのページにしか使わないコンポーネントを作る際の心理的な抵抗感を取り除くことです。上記の例でいうと、TitleDescription.vueのようなファイルは、URLでディレクトリを切らないと、なかなか作りにくいコンポーネントです。

もちろん、できるだけ共通で流用できるコンポーネントをつくるべきなのですが、ある程度ページが出そろうまでは、正直、どのように流用するか読めないことも多く、読んでも外れることも多く、共通化できることを狙って、時間をかけて作っても、意外と流用できないですし、一回しか使わないことも多いです。

なので、共通化できる状態になったら共通化すればよいので、まずはどこに配置すればいいのか迷うことなく、素早く作れるようにするというのが目指しているポイントです。共通化するタイミングになったら、近くの場所でcommonディレクトリをつくって、そこで共通化したコンポーネントを配置します。

# Base (assets/base_component) の説明

ここには、UIとして基本的なコンポーネントしか配置しません。Domainのものが入りこんでこないようにすることが最重要です。
順守しているルールは、**(1) モデルをpropsに持たないこと**、**(2) どこのページから使えること** です。

# ルールの進化

これは一見すると、3年前に提唱していたディレクトリ構成と同じです。

https://qiita.com/tsuyoshi_fukuzawa/items/a4aa482c5f6077812792

しかし、ルールの部分が進化しています。3年前には以下のルールがありました。

- Domainのコンポーネントは、Domainをさらに読み込めない。
- Pageは、DomainはURL配下のコンポーネントしか読み込めない。

これらがあると、以下の弊害が生まれました。

- 複雑なコードになったときに、どうしてもDomainを分割したい
- /usersと/projectで共通のDomainコンポーネントを使いたいときに詰んだ。

そのため、この2点のルールは消して、Domainは自由に分割してもよいし、URLを気にせず、別のところのコンポーネントを読み込んでいいということになりました。

さて、2023年のコンポーネント構成はこんな感じとなりました。個人的には、「このコンポーネントはBaseなのかDomainなのか」と悩むこともなく、「このコンポーネントはどのディレクトリにどんな名前でつくればいいのか」と悩むことなく、すんなりとコンポーネントを切って、つくっていけるので満足しております。

では、今日はこれでおしまいです。明日のMetapsカレンダーもお楽しみに。それではよい一日を。