フロントエンド開発ドキュメント(vue/ts vuetify)
はじめに
vue
,ts
,vuetify
のフロントエンド開発の知見がたまってきたためコード規約をまとめてみた。
開発環境
以下の開発環境を参考に(自身が作成した開発環境テンプレート)話を進めていく。
フォルダ構造等の情報は、README
に記載。
共通
基本ルール
-
原則公式サイトのルールに準ずる
基本の言語ルールは、公式サイトに準ずる。
その中で、以降記載のルールを定義する。typescript 公式
typescript 入門
typescript コーディング規約
Vue 公式
Vue コーディング規約
Vuetify 公式
命名規則
-
原則開発言語のルールに準ずる
だれでも開発しやすいように使用する言語の命名規則にしたがって開発していく。 -
品詞を意識
日本語を入れることは原則禁止し、品詞を意識しながら開発する。
省略した言葉は使用せず不要な単語は入れない。
ただ、不要な単語を入れないように意識しすぎて可読性が下がることがないようにする。
それにより多少名前が長くなることは問題ない。 -
配列,リスト名
〇〇List
のようにするのではなく〇〇s
とする。 -
エレメントイベントに対する関数名
通常の関数とエレメントイベントとの差別化のためにon{イベント名}〇〇
のように作成する。// クリックイベント cosnt onClickSaveData = () =>{}
カラー定義
-
16 進数カラーコードを使用する
色指定時は、16 進数カラーコード(HEXA)を使用する。
開発ドキュメント[Typescript]
コメント
-
TODO の付け方
だれのタスクかをわかりやすくするため以下のように自身の名前を入れて記述する/** * 文字列出力 * @returns */ const exportString = (): void => { // TODO:名前 後で処理を書く //固定文字列を返却 return "Hello World"; };
-
変数/関数には、doc コメント を使用
変数、関数を定義した際のコメントは必ず doc コメントで書く。
関数内の処理は普通のコメントで書く。
関数の doc コメントに@param
は不要。
@returns
は明記する。
/**
* 文字列出力
* @returns
*/
const exportString = (): void => {
//固定文字列を返却
return "Hello World";
};
環境変数定義
-
.env
ファイルに定義する
プロジェクトフォルダ内の以下ファイルが環境変数定義ファイル。
.env.development
:開発用
.env.production
:本番用用環境変数の型定ファイルは以下に配置してあるので、環境変数を定義したら合わせて追記するようにする。
types/env.d.ts
詳しい内容については、公式を参照。
変数定義
-
var
を使わない
const
またはlet
を使用する。
使用しない理由として、再宣言が可能なため意図しない処理をする可能性があるため。 -
型定義を書く
以下のように必ず型定義を書く。
/**
* 文字列型宣言
*/
const sampleString: string = "Hello world";
-
文字列結合はテンプレートリテラルを使用
可読性が上がるため。// NG const sample = (text: string): void => { return "hello " + text; }; // OK const sample = (text: string): void => { return "hello ${text}"; };
関数定義
-
function
を使わずアロー関数を使用
使用しない理由-
this
のバインディング
アロー関数は周囲のスコープのthis
を継承し、意図しないthis
の変更を防ぐ。 - クラスメソッドでの安定性
クラス内で意図しないthis
の問題を防ぐ。 - 簡潔な構文
記述が短くなり、コードの可読性が向上する。
-
-
引数はオブジェクト形式で定義する
引数をオブジェクト形式で受け取ることで、名前付き引数のようにわかりやすく指定できる。
呼び出すときに名前付きで指定できるので分かりやすい。
オプショナルにするときは?
を用いて、関数内でundefined
の対応を行う。const sample = (argument: { /** ID */ id: number; /** 名前 */ name: string?; }): void => { // nameのundefined対応 };
-
戻り値は必ず指定する
戻り値がvoid
の場合は、省略可。
非同期関数の場合はPromise<型>
。const sample = async (): Promise<number> => { // 非同期処理 };
定数定義
-
宣言方法
ネームスペースConsts
で囲み、以下のように宣言する。
このような宣言の利点は、Object.values(Consts.XXX)
で値の配列取得が可能となるから。
画面でのみ使用する共通化しない定数は不要。
言語に準ずるのであれば、定数とわかるように大文字スネークケースするが
日本人は大文字英語に慣れていないないため、可読性が少し落ちる。
それに、IDE やエディタの発達により定数かどうかを見分けることが用意になった。
以上のことをふまえ、ロワーキャメルケースで定義する。/** 定数 */ namespace Consts { /** 共通で使用する定数 */ export const sample1 = { AAA: "aaa", BBB: "bbb", CCC: "ccc", } as const; export type Role = (typeof Role)[keyof typeof Role]; /** 画面でのみ使用する共通化しない定数 */ const sample2 = { AAA: "aaa", BBB: "bbb", CCC: "ccc", } as const; }
反復処理
-
ループは基本 for-of に統一
foreach
はループ内でawait
が使用できないため使用しない。
index
が欲しい場合はfor-in
。const samples = [1, 2, 3, 4, 5]; // for-of for (let item of samples) { console.log(item); } // for-in for (const key in obj) { console.log(key, obj[key]); }
比較演算
-
厳密比較演算子を使用する
値または型が異なるかを比較するため、より厳格な比較を行える。
boolean
型をtrue
判定する場合でも=== true
とする。console.log(1 != "1"); // false (型変換が行われるため) console.log(true != 0); // true console.log(1 !== "1"); // true (型が異なるため) console.log(true !== 1); // true
-
違う型同士で比較しない
以下のように数値と文字列の比較などができるが、予期しない結果が出てくる可能性があるため禁止。console.log(10 > "5"); console.log("10" < 20);
-
論理否定演算子を使用しない
!
を許容すると、いろいろな書き方がソース上で存在する可能性がある。
その場合、可読性がおちてしまうため、書き方を統一する。// NG if (!isExists) { } // OK if (isExists === false) { }
日付
-
Date 型の比較は getTime で比較
比較演算子は問題なく動作するが、等価と不等価演算子の挙動がおかしくなる。
理由として、日付型は値型ではなくオブジェクト型であるため。
なので、Date
型をタイムスタンプ(number
型)に変換して比較する。const date1 = new Date("2025-01-01"); const date2 = new Date("2025-01-01"); console.log(date1.getTime() === date2.getTime()); // true console.log(date1.getTime() !== date2.getTime()); // false
未定義と値なし
-
未指定値は原則 null
undefined
とnull
の違いをしっかりと理解した上で使用する。
例外処理
-
エラーは握り潰さない
catch
してfalse
を返すなどで例外を握りつぶさない
共通のエラーハンドリングが存在するため、例外はthrow
する
catch
でthrow
しか行わないのであればcatch
自体不要
オプショナルチェイニング
-
仕様を理解した上で使う
オプショナルチェイニングは、短絡評価を行うため、後続の呼び出しがスキップされる。
そのことを踏まえた上で使用しないと意図しないエラーを起こす可能性がある。
また、本来は必ず値があるべきという場所で?.
を使うと、エラーを握りつぶしてしまうため、エラーが発見しづらくなる。
オプショナルチェイニングは「このプロパティ(またはメソッド)が本当になくてもいい(あったら使うが、なくてもエラーにしない)」という認識のもと意識しながら使用してほしい。
Null
合体演算子,論理代入演算子
-
原則使用しない
論理代入演算子や null 合体演算子の仕様を把握していないとオプショナルチェイニングと同じで、変数やプロパティが意図せず書き換えられる可能性がある。
また、他の演算子と合わせると(例:x ||= y ??= z;
)みにくく、if 文などで書いた方が良い。
慣れている人ならば良いが、あまり見慣れていない人からすると誤った使用をしたり可読性が低下するので原則使用しない。
非 null アサーション演算子
-
むやみやたらに使用しない
null
でないことを確認して使用する。const value: string | null = null; if (value !== null) { console.log(value!.toUpperCase()); }
モジュール呼び出し
-
相対パスは使用しない。
パッケージ以外のimport
は@
を利用するimport { getSamples } from "@/services/sampleService";
-
default export
の禁止
default export
は各所で名前を統一することが難しい。
原則、名前付きexport
とする。
開発ドキュメント[Vue]
リアクティブオブジェクト
-
type 宣言はしない
type
宣言とref
の型付けの2つが存在するとみにくいため、ジェネリクスにのみ型を宣言する。const sample = ref<string | null>(null);
-
reacitive
オブジェクトは使用しない
リアクティブには、ref
とreactive
が存在するが以下の点からref
を使用し原則reactive
を使用しない。
条件付きレンダリング
-
v-if
とv-for
を併用しない
同じ要素にv-if
とv-for
を付与するとv-if
の方が先に評価されてしまうため併用しない。
リストレンダリング
-
キー属性は必ず付ける
キー属性を付与しなければ、リアクティブなリスト値が変更された時、レンダリングの不整合がおきてしまうため。// NG <script setup lang="ts"> const samples = ref<string[]>(["あ","い","う"]); </script> <template> <div v-for="sample of samples">{{sample}}</div> </template>
// OK <script setup lang="ts"> const samples = ref<string[]>(["あ","い","う"]); </script> <template> <div v-for="sample of samples" :key="sample">{{sample}}</div> </template>
-
キー属性に
index
を使用しない
上記記載内容と同様、レンダリングの不整合がおきてしまうため。 -
for-of
を使用する
for-in
でもfor-of
でもいけるがtypescript
と合わせるようにfor-of
を使用する。 -
リアクティブな値の変更イベントは、watch を使用
input
タグなどのchange
イベントで変更値を取得することができるが、原則watch
でリアクティブな値を監視して処理を実行させる。
リアクティブに値が入ってくるのにchange
イベントの変更値を取る必要がないため。// NG // リアクティブ値 <script setup lang="ts"> const sample = ref<string | null>(null); // 変更イベント const changeInput = (changeValue: string): void => { // 処理 } </script> <template> <input @change="changeInput" v-model="sample" /> </template>
// OK <script setup lang="ts"> // リアクティブ値 const sample = ref<string | null>(null); // 変更イベント watch(sample, async (newValue, oldValue) => { // 処理 }) </script> <template> <input v-model="sample" /> </template>
ウォッチャー
-
watchEffect
は原則禁止
内部の例アクティブ値の変更をすべて検知するため処理が追いづらい。
コンポーネント
-
defineProps
にwithDefaults
は使用しない
withDefaults
は、props
の初期値を設定できるが、使用せずに指定不要ならoptional
を使用してundefined
の対応を内部で行う。 -
双方向バインディングの値は
defineModel
を使用する
defineProps
内に定義できるが、他props
との差別化と型推論が効きやすくなる観点からdefineModel
を使用する。
defineProps
は、基本必須で定義する。 -
defineExpose は原則禁止
コンポーネント間の結合度が高くなり変更が大変になる可能性があるため。
開発ドキュメント[Vuetify]
リアクティブオブジェクト
-
入力系コンポーネントの
v-model
に渡すref
の型は必ずnull
を考慮する
clearable でのクリア時が null となるため<xxx | null>
のような型を宣言する。const textFiled = ref<string | null>(null);
例外として v-file-input で multiple を指定する場合はクリア時は空の配列となるので以下のように初期化。
const files = ref<File[]>([]);
ただし multiple を指定しない場合は通常通り null を考慮して以下のように初期化。
const files = ref<File[] | null>(null);
バリデーション
-
Rules
は基本的にtemplate
の内部で直接配列で定義
コンポーネントの検証をまとめたモジュールからバリデーションを呼び出し定義する。<v-text-field :rules="[textFieldRules.required]"></v-text-field>
検証を行うためには、以下のようにコンポーネントに
ref
を設定することで検証が可能になる。<script setup lang="ts"> // リアクティブ値 const name = ref<string | null>(null); // Form const form = ref<VForm>(); // クリックイベント const onClick = ():vold =>{ // 入力値検証 const validateResult = await form.value!.validate(); if (validateResult.valid === false) { // エラー処理 return; } } </script> <template> <v-btn @click="onClick">登録</v-btn> <v-form ref="form"> <v-text-field v-model="name" :rules="[textFieldRules.required]" ></v-text-field> </v-form> </template>
グリッドシステム
-
原則使用しない
<v-row>
や<v-col>
などのグリッドシステムは使用しない。
基本的に flex でレイアウトを組んでいく。
vuetify のスタイルクラス
-
原則使用しない
可読性を考慮するため、タグに直接style
を記述する。
共通で使用するものに関しては、class
を作成する。
Discussion