1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「あなたのVueファイルは読みづらい」と文句を言われたくない② -- コンポーネント&ディレクトリ設計編 --

Last updated at Posted at 2024-02-05

目次

1. 前書き
2. ディレクトリ構成
3. 共通のデータや処理を分離して管理するディレクトリ
4. 画面表示に関するディレクトリ
5. 後書き

現在の記事は②になります。①はこちらのリンクからどうぞ

1. 前書き

この記事のテーマは、「Vue ファイルの可読性を可能な限り上昇させる」です。コンポーネント設計ファイル分割の手法を通して、エンジニアに優しいコーディングをすることを目的としています。あくまで一意見としてなので、参考程度にお願いします。
もし、この記事を参考にしたコーディングをして同僚に文句を言われたら、是非その旨をこの記事のコメントに記入してください。 ひとつひとつのコメントに対して涙目になりながら中身を改善していきます。

自己紹介

自称フルスタックエンジニアの岩田と申します。業務の経験は 2023/6~ なので、まだ1年にも満たないミーハーな人間です。自分の経験が他のエンジニアさんの参考になれば良いなと思いながら記事を書いていますので、もし良ければ他の記事もご覧ください!!

概要

コーディングスタイルの統一は、チーム開発において1つの大事な要素になっていると思います。

例えばですが、一つのページにつきコンポーネントファイルが一つだとすると、コードの行数が少なくても 100 行前後、多い時だと 500 行近くに達することがあります。これに関しては Vue のエンジニアに限らず React のエンジニアでも起こりうることだと思います。そんなコードのファイルに当たった時、我々のエンジニアとしてのエネルギーは即座に失われてしまうことでしょう。そんなことがないように、適切なファイル分割をし、可読性の高いファイル構成にする必要があります。

この記事では、僕自身が考案したコンポーネント設計の考え方や、ファイル分割の考え方について紹介していこうと思います。

この記事の対象者

・Vue.js のコーディングに慣れてきて、次のステップに進みたい方
・ある程度の規模感になってくると、途端にコードが読みづらくなってしまうと感じている方

あくまで一個人のコーディングスタイルなので参考程度にしてくださればと思っています。

2. ディレクトリ構成

今回考えたい簡単なページ構成をここで提示しておきます。

パス 画面の説明
*/users ユーザーの一覧画面 (検索機能付き)
*/users/:id ユーザーの詳細 & 編集画面 (:idには任意のユーザー ID が入ります)
*/blogs ブログ一覧画面 (検索機能付き)

その上で、以下のようなディレクトリ構造にしました。
僕がディレクトリ構成を考える際、パスとディレクトリ構成を合わせるということは強く意識をして作成しています。

ディレクトリ構成
src/
├── components/
│   ├── common/
│   │   ├── feedbacks/
│   │   ├── inputs/
│   │   └── wrappers/
│   └── domains/
│       ├── users/
│       │   ├── index/
│       │   │   ├── UsersSearchWindow.vue
│       │   │   └── UsersListItem.vue
│       │   └── show/
│       │       └── UserInfo.vue
│       └── blogs/
│           └── index/
│               ├── BlogsSearchWindow.vue
│               └── BlogsList.vue
├── constants/
│   ├── commonData.ts (jsも可)
│   ├── usersData.ts (jsも可)
│   └── blogsData.ts (jsも可)
├── hooks/
│   ├── users/
│   │   ├── getUsers.ts (jsも可)
│   │   └── getUserDetail.ts (jsも可)
│   └── blogs/
│       └── getBlogs.ts (jsも可)
├── pages/
│   ├── users/
│   │   ├── Index.vue
│   │   └── Show.vue
│   └── blogs/
│       └── Index.vue
└── types/  #TypeScriptを使用する場合
    ├── users.d.ts
    └── blogs.d.ts

次に各ディレクトリについて説明していきます。

3. 共通のデータや処理を分離して管理するディレクトリ

ここでは3つのディレクトリに焦点を当てて説明していこうと思います。簡単な説明を以下に載せておきます。

ディレクトリ 説明
constants プロジェクト全体で共通の定数をまとめておく
hooks 一定の処理(script ファイルに書く)を分離しておく
types プロジェクト全体で使い回したい型を定義しておく

基本的にこれらのファイルは、.vue ファイルの script タグ内に書いて同様の結果を得ることができるため別ファイルに分離しなくても問題ないです。ただ、なにもかも.vue ファイルに書いてしまうとファイルが肥大化して可読性が下がってしまうので、敢えて分離しています。

それでは、各ディレクトリについて詳しく説明していこうと思います。

3-1. constants

全体で共通のデータをここで定義します。
今回のようなユーザーとブログのアプリケーションだと必要なさそうなので、フランチャイズ展開している飲食店を例にしてみると、

  • 店舗一覧
  • メニュー一覧

といったデータは基本的には不変ですし、全体で使用したくなりそうなものではあります。
※ もちろんプロダクトの性質によって差はあります。

*Data.ts
export const stores = [
    {
        id: 1,
        name: '東京本店',
            :
            :
    },
        :
        :
]
*.vue
<script setup>
import { stores } from '*Data';
</script>

3-2. hooks

以下のようなケースで、このファイル内に分離してあげます

  • API 通信 (外部の DB との通信など)
  • カスタムイベントの実装
  • フォームの状態管理 (React の場合)

例えば、FireBase からデータを引っ張ってくる処理の場合、

hooks/users/getUsers.ts
import { ref, onMounted } from 'vue';
import axios from 'axios';

export const getUsers = () => {
    const users = ref([]);
    const isLoading = ref(false);
    const error = ref(null);

    const fetchUsers = async () => {
        isLoading.value = true;
        try {
            const response = await axios.get('ここにURL');
            users.value = response.data;
        } catch (err) {
            error.value = err;
        } finally {
            isLoading.value = false;
        }
    };

    onMounted(fetchUsers);

    return { users, isLoading, error };
};
pages/users/Index.vue
<script setup>
import { getUsers } from '/hooks/users/getUsers';

const { users, isLoading, error } = getUsers();
</script>

こんな形で記述することができ、ご覧の通り.vue ファイル内の記述が非常にスッキリしました。何度もしつこいですが全てをpages/users/Index.vueに書いても同様の機能を実装できますが、コードの可読性という点においてどちらが良いのかは一目瞭然ですよね。

3-3. types

types/users.d.ts
export interface Users {
    id: number;
    name: string;
    email: string;
        :
        :
}
        :
        :
pages/users/Index.vue
<script setup>
import { Users } from '/types/users';
</script>

4. 画面表示に関するディレクトリ

次に残りの2つのディレクトリに焦点を当てて説明していこうと思います。簡単な説明を以下に載せておきます。

ディレクトリ 説明
pages データの取り込みとルーティング先のプラットフォーム
components 画面に表示する要素の部品を作る

それでは、各ディレクトリについて詳しく説明していこうと思います。

4-1. pages

このディレクトリ内のファイルの役割は

  • ルーティングの指定先になる
  • データを集積する
  • 子コンポーネントにデータを分配する

つまり、ルールとしては単純で、

  • ルーティング先として指定できるファイルは pages ディレクトリにあるファイルのみ
  • pages ディレクトリ配下の.vue ファイルでは、template タグ内でtemplate タグ子コンポーネントのみを並べ、その他の HTML は書かない。
pages/users/Index.vue (悪い例)
<script setup>
import { UsersSearchWindow } from '/components/domains/users/UsersSearchWindow.vue'
import { UsersListItem } from '/components/domains/users/UsersListItem.vue'

const users = ~
</script>
<template>
    <h1>タイトル</h1>
    <UsersSearchWindow />
    <div v-for="user in users" :key="user.id">
        <h3>ユーザー一覧</h3>
        <UsersListItem :user="user" />
    </div>
</template>
pages/users/Index.vue (良い例)
<script setup>
import { UsersSearchWindow } from '/components/domains/users/UsersSearchWindow.vue'
import { UsersListItem } from '/components/domains/users/UsersListItem.vue'

const users = ~
</script>
<template>
    <UsersSearchWindow />
    <template v-for="user in users" :key="user.id">
        <UsersListItem :user="user" />
    </template>
</template>

あとこれは個人的なルールなのですが、pages配下の.vueファイルには、一定の名前しかつけないようにしています。これは大した理由はないのですが、考え方としては、

  • ディレクトリ構成でファイルのジャンル
  • .vueファイルの名前で役割

を示すようにしています。

ファイル名 説明
Index.vue 一覧表示画面
Show.vue 詳細表示画面
Create.vue 新規作成画面
Edit.vue 編集画面

なので、例えば「ユーザーのブログランキング」についてのページを作るということであれば、pages/users/Ranking.vueではなく、pages/users/ranking/Index.vueとして作成するということになります。ファイル名やディレクトリ構成になるべく多くの情報を詰め込もうという魂胆です。

4-2. components

ここには pages ディレクトリ内のファイルから呼び出される UI の部品を作っていきます。2つのディレクトリがあるので、それぞれについて説明します。

ディレクトリ 説明
common 全ページ共通で使うコンポーネントを置いておく
domains 特定のページ内の UI の部品を置いておく(domain 配下は他のディレクトリと同じ設計)

common

ここには、さまざまなファイルで使用したいコンポーネントを作成します。

  • feedbacks
    • inputsやwrappersに該当しないもの
    • スナックバー(トースター) など
  • inputs
    • フォームに関するもの
    • フォームの部品のコンポーネント など
  • wrappers
    • 囲いになるもの(<slot />を使ってコンポーネントの中身を入れていく)
    • ダイアログ(モーダル)の骨格 など

domains

ここでは、特定のページの特定のパーツとして使用したいコンポーネントを作成します。
基本的に、1ページの中を機能ごとに分割し、ここに書く機能ごとのコンポーネントファイルを作成していく形になります。

先ほどのUsersListItemを例にして作ってみると、

components/domains/users/UsersListItem.vue
<script setup>
const props = defineProps({
    user: Object
})
    :
    :
</script>

のような感じになります。

5. 後書き

5-1. コードの品質の重要性

チーム開発においてコードの品質を高く保つためには、

コーディングルールを徹底する
可読性を上げるような工夫をする
この2点が最重要だと思います。正直この辺りが疎かになると、たとえ2人の開発でもごちゃごちゃになります。さらに人が増えるとなると尚更です。こういったルールを身体に染み込ませることにもある程度の工数は掛かってしまいますが、その後の保守のことを考えるとプラスの要素しかないですよね。
少しだけコーディングルールを見直してみてはいかがでしょうか。

コーディングを快適にするためのルール作りにこの記事の内容が参考になっていることを切に願っています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?