# スタイルガイド
このドキュメントは、 Vue 固有の記法についての公式なスタイルガイドです。もしあなたがプロジェクトにおいて Vue を使用する場合は、エラーや有益でない議論、アンチパターンを避けるための参考となります。しかし、スタイルガイドはすべてのチームやプロジェクトで理想とは限らないと考えていますので、過去の経験や、周囲の技術スタック、個人の価値観に基づいた上で必要に応じて慎重に逸脱することが推奨されます。
ほとんどのパートにおいて、基本的に JavaScript や HTML に対する提案は避けています。セミコロンやカンマの使用の是非はどちらでも良いです。 HTML の属性に対してシングルクォートかダブルクォートどちらかを利用するかもどちらでも良いです。しかし、 Vue のコンテキストにおいて特定のパターンが役立つと判明した場合については、その限りではありません。
最後に、私たちはルール群を 4 つのカテゴリに分割しました:
# ルールカテゴリ
# 優先度 A: 必須
これらのルールはエラー防止に役立ちます。ですので、学び、遵守してください。例外は存在するかもしれませんが、それらは極めて稀で、かつ JavaScript と Vue の両方の専門知識を持った人によってのみ作られるべきです。
# 優先度 B: 強く推奨
これらのルールは、ほとんどのプロジェクトで読みやすさや開発者の体験をよりよくするために見いだされました。これらに違反してもあなたのコードは動きますが、ごくまれなケースで、かつちゃんと正当を示した上でのみ違反するようにすべきです。
# 優先度 C: 推奨
同じくらい良いオプションが複数ある場合、一貫性を確保するために任意の選択をすることができます。これらのルールでは、それぞれ許容可能なオプションを説明し、既定の選択を提案します。つまり、一貫性があり、正当な理由を持ち続ける限り、独自のコードベースで自由に異なる選択肢を作ることができます。ですが、正当な理由を必ず持つようにしてください!コミュニティの標準に合わせることで、あなたは:
- 直面するコミュニティのコードを容易に理解できるように脳を慣れさせます。
- ほとんどのコミュニティのコードサンプルを変更なしにコピーして貼り付ける事ができます。
- 少なくとも Vue に関しては、ほとんどの場合、新たな人材はあなたのコーディングスタイルよりも既に慣れ親しんだものを好みます。
# 優先度 D: 注意して使用
Vue のいくつかの機能は、レアケースまたは従来のコードベースからスムーズな移行に対応するために存在します。しかしながらこれを使いすぎると、コードを保守することが難しくなり、またバグの原因になることさえあります。これらのルールは潜在的な危険な機能を照らし、いつ、なぜ避けなかればならないのかを説明しています。
# 優先度 A ルール: 必須
# 複数単語のコンポーネント名 必須
ルートの App
コンポーネントや、Vue が提供する <transition>
や <component>
のようなビルトインコンポーネントを除き、コンポーネント名は常に複数単語とするべきです。
全ての HTML 要素は 1 単語なので、このルールを守ることで既に存在する HTML 要素や将来定義される HTML 要素との 衝突を防止することができます (opens new window)。
悪い例
app.component('todo', {
// ...
})
2
3
export default {
name: 'Todo',
// ...
}
2
3
4
良い例
app.component('todo-item', {
// ...
})
2
3
export default {
name: 'TodoItem',
// ...
}
2
3
4
# Prop の定義 必須
Prop の定義は可能な限り詳細にするべきです。
コミットされたコードでは、prop の定義は常に可能な限り詳細にすべきで、少なくともタイプの指定をする必要があります。
詳細な説明
詳細な プロパティの定義 には 2 つの利点があります:
- コンポーネントの API が明文化されるため、そのコンポーネントの使用方法が簡単に確認できます。
- 開発中、コンポーネントに対して誤った形式のプロパティが提供されると Vue は警告を通知するため、潜在的なエラー原因の検知に役立ちます。
悪い例
// プロトタイピングの時に限り OK
props: ['status']
2
良い例
props: {
status: String
}
2
3
// さらに良いです!
props: {
status: {
type: String,
required: true,
validator: value => {
return [
'syncing',
'synced',
'version-conflict',
'error'
].includes(value)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# キー付き v-for
必須
v-for
に対しては常に key
を使用してください。
サブツリーの内部コンポーネントの状態を維持するために、コンポーネントでの v-for
には 常に key
を付ける必要があります。ただし要素の場合であっても、アニメーションでの オブジェクトの一貫性 (opens new window) のように、予測可能な振る舞いを維持することをおすすめします。
詳細な説明
TODO リストを持っているとしましょう:
data() {
return {
todos: [
{
id: 1,
text: 'Learn to use v-for'
},
{
id: 2,
text: 'Learn to use key'
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
次に、それらをアルファベット順に並べ替えます。 DOM を更新するとき、可能な限り安価な DOM の変更を行うために Vue はレンダリングを最適化します。 これは、最初の todo 要素を削除して、再度リストの最後に追加することを意味するかもしれません。
問題は、DOM に残る要素を削除しないことが重要となる場合があることです。 例えば、 <transition-group>
を使用してリストの並べ替えをアニメーション化する場合だったり、レンダリングされた要素が <input>
の時はフォーカスを維持したいといった場合があります。 このような場合、アイテムごとに一意のキー (:key="todo.id"
など) を追加することで、 Vue に対してどうしたらより予期した通りの動作ができるかを伝えることができます。
これまでの経験から、あなたとあなたのチームがこれらのエッジケースについて心配する必要がないように、 常に 一意のキーを追加することをおすすめします。 その上で、オブジェクトの一貫性が必要なくてパフォーマンスが重要な稀なシナリオにおいては、意識的な例外を作成すると良いでしょう。
悪い例
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
2
3
4
5
良い例
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
2
3
4
5
6
7
8
# v-for
と一緒に v-if
を使うのを避ける 必須
v-for
と同じ要素に v-if
を決して使わないでください。
こうしたくなる一般的なケースが 2 通りほどあります:
リストのアイテムをフィルタリングする時 (
v-for="user in users" v-if="user.isActive"
のように)。このような場合、users
をフィルタリングをされたリストを返す新しい算出プロパティ (例えばactiveUsers
) に置き換えます。リストを非表示にする必要がある場合に、リストがレンダリングされるのを避ける時 (
v-for="user in users" v-if="shouldShowUsers"
のように)。このような場合、v-if
をコンテナ要素 (例えばul
,ol
)に移動します。
詳細な説明
Vue がディレクティブを処理する場合、v-if
は v-for
よりも優先度が高いため、次のようなテンプレートは:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
2
3
4
5
6
7
8
9
v-if
ディレクティブが最初に評価され、反復変数の user
がこの時点では存在しないためエラーが投げられます。
これは、代わりに算出プロパティを元に反復処理をすることで修正できます。次のようになります:
computed: {
activeUsers() {
return this.users.filter(user => user.isActive)
}
}
2
3
4
5
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
2
3
4
5
6
7
8
または、 v-for
と一緒に <template>
タグを使用して、 <li>
要素をラップすることもできます:
<ul>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
2
3
4
5
6
7
悪い例
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
2
3
4
5
6
7
8
9
良い例
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
2
3
4
5
6
7
8
<ul>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
2
3
4
5
6
7
# コンポーネントスタイルのスコープ 必須
アプリケーションにとって、トップレベルの App
コンポーネントとレイアウトコンポーネントのスタイルはグローバルである可能性がありますが、他のすべてのコンポーネントは常にスコープ化されているべきです。
これは、単一ファイルコンポーネント のみに関連します。scoped
属性 (opens new window) の使用は必須_ではありません_。 スコープは CSS modules (opens new window) や BEM (opens new window) のようなクラスに基づいた戦略、または他のライブラリ/慣例を介して行うことができます。
ただしコンポーネントライブラリでは、 scoped
属性を使用するのではなく、クラスに基づいた戦略を優先すべきです
これにより、人間が読み取りやすいクラス名を使って、内部のスタイルを上書きすることが容易になります。またそのクラス名は、高い特定性を持たないけれど、依然として競合が発生する可能性が低いままのものになります。
詳細な説明
大規模なプロジェクトを開発している場合や他の開発者と一緒に開発している場合、またはサードパーティの HTML/CSS (Auth0 など) を含んでいる場合は、一貫したスコープによってスタイルが対象のコンポーネントのみに適用されることが保証されます。
scoped
属性以外にも、一意のクラス名を使用することでサードパーティの CSS が独自の HTML に適用されないことを保証しやすくできます。例えば、多くのプロジェクトでは button
や btn
、または icon
といったクラス名を使用しているため、BEM などの戦略を使用していない場合でも、アプリ固有かつ/またはコンポーネント固有(例: ButtonClose-icon
)のプレフィックスを追加することで、ある程度の保護を提供できます。
悪い例
<template>
<button class="btn btn-close">×</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
良い例
<template>
<button class="button button-close">×</button>
</template>
<!-- `scoped` を使用 -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<button :class="[$style.button, $style.buttonClose]">×</button>
</template>
<!-- Using CSS modules -->
<style module>
.button {
border: none;
border-radius: 2px;
}
.buttonClose {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<button class="c-Button c-Button--close">×</button>
</template>
<!-- BEM の慣例を使用 -->
<style>
.c-Button {
border: none;
border-radius: 2px;
}
.c-Button--close {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# プライベートなプロパティ名 必須
モジュールスコープを使用して、外部からプライベート関数にアクセスできないようにします。それが不可能な場合は、パブリック API と見なすべきではないプラグインやミックスインなどのカスタムプライベートプロパティに、常に $_
のプレフィックスを使用してください。 その上で、他の作成者によるコードとの競合を避けるために、名前付きスコープも含めるようにしてください (例: $_yourPluginName_
)
詳細な説明
Vue は _
のプレフィックスを使用して独自のプライベートプロパティを定義するため、同じプレフィックス (_update
など) を使用すると、インスタンスプロパティが上書きされるリスクがあります。 Vue が現在特定のプロパティ名を使用していないことを確認したとしても、それ以降のバージョンで競合が発生しないという保証はありません。
$
のプレフィックスに関しては、Vue エコシステム内でのそのプレフィックスの目的は、ユーザーに公開される特別なインスタンスプロパティであるため、_独自_プロパティに使用することは適切ではありません。
代わりに、Vue との競合がないことを保証するユーザー定義のプライベートプロパティの規則として、2 つのプレフィックスを $_
に結合することをおすすめしています。
悪い例
const myGreatMixin = {
// ...
methods: {
update() {
// ...
}
}
}
2
3
4
5
6
7
8
const myGreatMixin = {
// ...
methods: {
_update() {
// ...
}
}
}
2
3
4
5
6
7
8
const myGreatMixin = {
// ...
methods: {
$update() {
// ...
}
}
}
2
3
4
5
6
7
8
const myGreatMixin = {
// ...
methods: {
$_update() {
// ...
}
}
}
2
3
4
5
6
7
8
良い例
const myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update() {
// ...
}
}
}
2
3
4
5
6
7
8
// さらに良いです!
const myGreatMixin = {
// ...
methods: {
publicMethod() {
// ...
myPrivateFunction()
}
}
}
function myPrivateFunction() {
// ...
}
export default myGreatMixin
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 優先度B のルール: 強く推奨
# コンポーネントのファイル 強く推奨
ファイルを結合してくれるビルドシステムがあるときは必ず、各コンポーネントはそれぞれ別のファイルに書くべきです。
そうすれば、コンポーネントを編集したり使い方を確認するときにより素早く見つけることができます。
悪い例
app.component('TodoList', {
// ...
})
app.component('TodoItem', {
// ...
})
2
3
4
5
6
7
良い例
components/
|- TodoList.js
|- TodoItem.js
2
3
components/
|- TodoList.vue
|- TodoItem.vue
2
3
# 単一ファイルコンポーネントのファイル名の形式 強く推奨
単一ファイルコンポーネント のファイル名は、すべてパスカルケース (PascalCase) にするか、すべてケバブケース (kebab-case) にするべきです。
パスカルケースは、JS(X) やテンプレートの中でコンポーネントを参照する方法と一致しているので、コードエディタ上でオートコンプリートが可能な場合はとてもうまく働きます。しかし、大文字と小文字が混ざったファイル名は、大文字と小文字を区別しないファイルシステム上で時々問題を起こす可能性があります。そのため、ケバブケースもまた完全に受け入れられています。
悪い例
components/
|- mycomponent.vue
2
components/
|- myComponent.vue
2
良い例
components/
|- MyComponent.vue
2
components/
|- my-component.vue
2
# 基底コンポーネントの名前 強く推奨
アプリケーション特有のスタイルやルールを適用する基底コンポーネント (またはプレゼンテーションコンポーネント: Presentation Components、ダムコンポーネント: Dumb Components、純粋コンポーネント: Pure Components とも) は、すべて Base
、App
、V
などの固有のプレフィックスで始まるべきです。
詳細な説明
これらのコンポーネントは、あなたのアプリケーションに一貫したスタイルやふるまいをもたせる基礎として位置づけられます。これらは、おそらく以下のものだけを含むでしょう:
- HTML 要素、
- 別の基底コンポーネント、そして
- サードパーティ製の UI コンポーネント
しかし、それらにはグローバルな状態(例:Vuex ストアからのもの)は含まれません。
これらのコンポーネントの名前は、しばしばラップしている要素の名前を含みます(例えば BaseButton
、BaseTable
)。それ特有の目的のための要素がない場合は別ですが(例えば BaseIcon
)。もっと特定の用途に向けた同じようなコンポーネントを作る時は、ほとんどすべての場合にこれらのコンポーネントを使うことになるでしょう。(例えば BaseButton
を ButtonSubmit
で使うなど)
このルールの長所:
エディタ上でアルファベット順に並べられた時に、アプリケーションの基底コンポーネントはすべて一緒にリストされ、識別しやすくなります。
コンポーネントの名前は常に複数単語にするべきなので、このルールによってシンプルなコンポーネントラッパーに勝手なプレフィックスを選ばなければならない(例えば
MyButton
、VueButton
)ということがなくなります。これらのコンポーネントはとても頻繁に使われるので、あらゆる場所で import するよりも単純にグローバルにしてしまいたいと思うかもしれません。プレフィックスを利用して、それを Webpack でできるようになります。
const requireComponent = require.context("./src", true, /Base[A-Z]\w+\.(vue|js)$/) requireComponent.keys().forEach(function (fileName) { let baseComponentConfig = requireComponent(fileName) baseComponentConfig = baseComponentConfig.default || baseComponentConfig const baseComponentName = baseComponentConfig.name || ( fileName .replace(/^.+\//, '') .replace(/\.\w+$/, '') ) app.component(baseComponentName, baseComponentConfig) })
1
2
3
4
5
6
7
8
9
10
11
悪い例
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
2
3
4
良い例
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
2
3
4
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
2
3
4
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
2
3
4
# 単一インスタンスのコンポーネント名 強く推奨
常に 1 つのアクティブなインスタンスしか持たないコンポーネントは、1 つしか存在しえないことを示すために The
というプレフィックスで始めるべきです。
これはそのコンポーネントが 1 つのページでしか使われないということを意味するのではなく、ページごとに 1 回しか使われないという意味です。これらのコンポーネントは、アプリケーション内のコンテキストではなく、アプリケーションに対して固有のため、決してプロパティを受け入れることはありません。もしプロパティを追加する必要があることに気づいたのなら、それは 現時点で ページごとに 1 回しか使われていないだけで、実際には再利用可能なコンポーネントだということを示すよい目印です。
悪い例
components/
|- Heading.vue
|- MySidebar.vue
2
3
良い例
components/
|- TheHeading.vue
|- TheSidebar.vue
2
3
# 密結合コンポーネントの名前 強く推奨
親コンポーネントと密結合した子コンポーネントには、親コンポーネントの名前をプレフィックスとして含むべきです。
もし、コンポーネントが単一の親コンポーネントの中でだけ意味をもつものなら、その関連性は名前からはっきりわかるようにするべきです。一般的にエディタはファイルをアルファベット順に並べるので、関連をもつものどうしが常に隣り合って並ぶことにもなります。
詳細な説明
この問題を、子コンポーネントを親コンポーネントの名前を元に命名したディレクトリの中に入れることで解決したいと思うかもしれません。例えば:
components/
|- TodoList/
|- Item/
|- index.vue
|- Button.vue
|- index.vue
2
3
4
5
6
もしくは:
components/
|- TodoList/
|- Item/
|- Button.vue
|- Item.vue
|- TodoList.vue
2
3
4
5
6
これは推奨されません。以下のような結果を生むからです:
- 同じような名前のファイルがたくさんできてしまい、コードエディタ上で素早くファイルを切り替えるのが難しくなります。
- ネストしたサブディレクトリがたくさんできてしまい、エディタのサイドバーでコンポーネントを参照するのに時間がかかるようになります。
悪い例
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
2
3
4
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
2
3
良い例
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
2
3
4
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
2
3
# コンポーネント名における単語の順番 強く推奨
コンポーネント名は、最高レベルの(たいていは最も一般的な)単語から始めて、説明的な修飾語で終わるべきです。
詳細な説明
あなたは疑問に思うかもしれません:
“なぜコンポーネント名に自然な言語でないものを使うように強制するのですか?”
自然な英語では、形容詞やその他の記述子は一般的に名詞の前に置かれ、そうでない場合には接続詞が必要になります。例えば:
- Coffee with milk
- Soup of the day
- Visitor to the museum
もちろん、あなたがそうしたいのならば、これらの接続詞をコンポーネント名に含めても構いませんが、それでも順番は重要です。
また、 何を「最高レベル」として尊重するかがアプリケーションの文脈になる ことに注意してください。例えば、検索フォームを持ったアプリケーションを想像してください。こんなコンポーネントがあるかもしれません:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
2
3
4
5
6
7
あなたも気づいたと思いますが、これではどのコンポーネントが検索に特有のものなのかとても分かりづらいです。では、このルールに従ってコンポーネントの名前を変えてみましょう。
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue
2
3
4
5
6
7
一般的にエディタではファイルはアルファベット順に並ぶので、コンポーネント間のあらゆる重要な関連性はひと目ではっきりと分かります。
あなたは、これを別の方法で解決したいと思うかもしれません。つまり、すべての検索コンポーネントは search ディレクトリの下に、すべての設定コンポーネントは settings ディレクトリの下にネストするという方法です。以下の理由から、とても大規模なアプリケーション(例えば 100 以上のコンポーネントがあるような)の場合に限ってこのアプローチを考慮することを推奨します:
- 一般的に、入れ子のサブディレクトリの中を移動するのは、単一の
components
ディレクトリをスクロールするのと比べて余分に時間がかかります。 - 名前の競合(複数の
ButtonDelete.vue
コンポーネントなど)により、コードエディタで特定のコンポーネントに素早く移動しづらくなります。 - 移動したコンポーネントへの相対参照を更新するには、検索と置換だけでは不十分な場合が多いため、リファクタリングはより困難になります。
悪い例
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
2
3
4
5
6
7
良い例
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
2
3
4
5
6
7
# 自己終了形式のコンポーネント 強く推奨
単一ファイルコンポーネント、文字列テンプレート、および JSX の中にある、中身を持たないコンポーネントは自己終了形式で書くべきです。ただし、DOM テンプレート内ではそうしてはいけません。
自己終了形式のコンポーネントは、単に中身を持たないだけでなく、中身を持たないことを 意図した ことだとはっきりと表現します。本の中にある白紙のページと、「このページは意図的に白紙のままにしています」と書かれたページとは違うということです。また、不要な閉じタグがなくなることであなたのコードはより読みやすくなります。
残念ながら、HTML はカスタム要素の自己終了形式を許していません。公式の「空」要素 (opens new window) だけです。これが、Vue のテンプレートコンパイラが DOM よりも先にテンプレートにアクセスして、その後 DOM の仕様に準拠した HTML を出力することができる場合にだけこの方策を使うことができる理由です。
悪い例
<!-- 単一ファイルコンポーネント、文字列テンプレート、JSX の中 -->
<MyComponent></MyComponent>
2
<!-- DOM テンプレートの中 -->
<my-component/>
2
良い例
<!-- 単一ファイルコンポーネント、文字列テンプレート、JSX の中 -->
<MyComponent/>
2
<!-- DOM テンプレートの中 -->
<my-component></my-component>
2
# テンプレート内でのコンポーネント名の形式 強く推奨
ほとんどのプロジェクトにおいて、単一ファイルコンポーネント と文字列テンプレートの中では、コンポーネント名は常にパスカルケース(PascalCase)になるべきです。 - しかし、 DOM テンプレートの中ではケバブケース(kebab-case)です。
パスカルケースには、ケバブケースよりも優れた点がいくつかあります:
- パスカルケースは JavaScript でも使われるので、エディタがテンプレート内のコンポーネント名を自動補完できます。
<MyComponent>
は<my-component>
よりも一単語の HTML 要素との見分けがつきやすいです。なぜなら、ハイフン 1 文字だけの違いではなく 2 文字(2 つの大文字) の違いがあるからです。- もし、テンプレート内で、Vue 以外のカスタム要素(例: Web コンポーネントなど)を使っていたとしても、パスカルケースは Vue コンポーネントがはっきりと目立つことを保証します。
残念ですが、HTML は大文字と小文字を区別しないので、DOM テンプレートの中ではまだケバブケースを使う必要があります。
ただし、もしあなたが既にケバブケースを大量に使っているのなら、HTML の慣習との一貫性を保ちすべてのあなたのプロジェクトで同じ型式を使えるようにすることはおそらく上にあげた利点よりも重要です。このような状況では、 どこでもケバブケースを使うのもアリです。
悪い例
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<mycomponent/>
2
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<myComponent/>
2
<!-- DOM テンプレートの中 -->
<MyComponent></MyComponent>
2
良い例
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<MyComponent/>
2
<!-- DOM テンプレートの中 -->
<my-component></my-component>
2
または
<!-- どこでも -->
<my-component></my-component>
2
# JS/JSX 内でのコンポーネント名の形式 強く推奨
JS/JSX 内でのコンポーネント名は常にパスカルケース(PascalCase)にするべきです。ただし、app.component
で登録したグローバルコンポーネントしか使わないような単純なアプリケーションでは、ケバブケース(kebab-case)を含む文字列になるかもしれません。
詳細な説明
JavaScript では、クラスやプロトタイプのコンストラクタは - 原則として異なるインスタンスを持ちうるものはすべて- パスカルケースにするのがしきたりです。Vue コンポーネントもインスタンスをもつので、同じようにパスカルケースにするのが理にかなっています。さらなる利点として、JSX(とテンプレート)の中でパスカルケースを使うことによって、コードを読む人がコンポーネントと HTML 要素をより簡単に見分けられるようになります。
しかし、app.component
によるグローバルコンポーネント定義だけを使うアプリケーションでは、代わりにケバブケースを使うことを推奨します。理由は以下の通りです:
- グローバルコンポーネントを JavaScript から参照することはほとんどないので、 JavaScript の原則に従う意味もほとんどありません。
- そのようなアプリケーションはたくさんの DOM 内テンプレートをもつのが常ですが、 そこでは ケバブケースを 必ず 使う必要があります
悪い例
app.component('myComponent', {
// ...
})
2
3
import myComponent from './MyComponent.vue'
export default {
name: 'myComponent',
// ...
}
2
3
4
export default {
name: 'my-component',
// ...
}
2
3
4
良い例
app.component('MyComponent', {
// ...
})
2
3
app.component('my-component', {
// ...
})
2
3
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}
2
3
4
# 完全な単語によるコンポーネント名 強く推奨
コンポーネント名には、略語よりも完全な単語を使うべきです。
長い名前によってもたらされる明快さは非常に貴重ですが、それをタイプする労力はエディタの自動補完によってとても小さくなります。特に、一般的でない略語は常に避けるべきです。
悪い例
components/
|- SdSettings.vue
|- UProfOpts.vue
2
3
良い例
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
2
3
# プロパティ名の型式 強く推奨
プロパティ名は、定義の時は常にキャメルケース(camelCase)にするべきですが、テンプレートや JSX ではケバブケース(kebab-case)にするべきです。
私たちは単純にこの慣習に従っています。JavaScript の中ではキャメルケースがより自然で、HTML の中ではケバブケースが自然です。
悪い例
props: {
'greeting-text': String
}
2
3
<WelcomeMessage greetingText="hi"/>
良い例
props: {
greetingText: String
}
2
3
<WelcomeMessage greeting-text="hi"/>
# 複数の属性をもつ要素 強く推奨
複数の属性をもつ要素は、1 行に 1 要素ずつ、複数の行にわたって書くべきです。
JavaScript では、複数のプロパティをもつ要素を複数の行に分けて書くことはよい慣習だと広く考えられています。なぜなら、その方がより読みやすいからです。Vue のテンプレートや JSX も同じように考えることがふさわしいです。
悪い例
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
<MyComponent foo="a" bar="b" baz="c"/>
良い例
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>
2
3
4
<MyComponent
foo="a"
bar="b"
baz="c"
/>
2
3
4
5
# テンプレート内での単純な式 強く推奨
複雑な式は算出プロパティかメソッドにリファクタリングして、コンポーネントのテンプレートには単純な式だけを含むようにするべきです。
テンプレート内に複雑な式があると、テンプレートが宣言的ではなくなります。私たちは、どのように その値を算出するかではなく、何が 表示されるべきかを記述するように努力するべきです。また、算出プロパティやメソッドによってコードが再利用できるようになります。
悪い例
{{
fullName.split(' ').map((word) => {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
2
3
4
5
良い例
<!-- テンプレート内 -->
{{ normalizedFullName }}
2
// 複雑な式を算出プロパティに移動
computed: {
normalizedFullName() {
return this.fullName.split(' ')
.map(word => word[0].toUpperCase() + word.slice(1))
.join(' ')
}
}
2
3
4
5
6
7
8
# 単純な算出プロパティ 強く推奨
複雑な算出プロパティは、できる限りたくさんの単純なプロパティに分割するべきです。
詳細な説明
単純な、よい名前を持つ算出プロパティは:
テストしやすい
それぞれの算出プロパティが、依存がとても少ないごく単純な式だけを含む場合、それが正しく動くことを確認するテストを書くのがより簡単になります。
読みやすい
算出プロパティを単純にするということは、たとえそれが再利用可能ではなかったとしても、それぞれに分かりやすい名前をつけることになります。それによって、他の開発者(そして未来のあなた)が、注意を払うべきコードに集中し、何が起きているかを把握することがより簡単になります。
要求の変更を受け入れやすい
名前をつけることができる値は何でも、ビューでも役に立つ可能性があります。例えば、いくら割引になっているかをユーザに知らせるメッセージを表示することに決めたとします。 また、消費税も計算して、最終的な価格の一部としてではなく、別々に表示することにします。
小さく焦点が当てられた算出プロパティは、どのように情報が使われるかの決めつけをより少なくし、少しのリファクタリングで要求の変更を受け入れられます。
悪い例
computed: {
price() {
const basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
2
3
4
5
6
7
8
9
良い例
computed: {
basePrice() {
return this.manufactureCost / (1 - this.profitMargin)
},
discount() {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice() {
return this.basePrice - this.discount
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 引用符付きの属性値 強く推奨
空ではない HTML 属性の値は常に引用符(シングルコーテーションかダブルコーテーション、 JS の中で使われていない方)でくくるべきです。
HTML では、空白を含まない属性値は引用符でくくらなくてもよいことになっていますが、そのせいで空白の使用を 避けてしまい 属性値が読みづらくなりがちです。
悪い例
<input type=text>
<AppSidebar :style={width:sidebarWidth+'px'}>
良い例
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
# ディレクティブの短縮記法 強く推奨
ディレクティブの短縮記法 (v-bind:
に対する :
、v-on:
に対する @
、v-slot:
に対する #
)は、常に使うか、まったく使わないかのどちらかにするべきです。
悪い例
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
2
3
4
<input
v-on:input="onInput"
@focus="onFocus"
>
2
3
4
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
2
3
4
5
6
7
良い例
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
2
3
4
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
2
3
4
<input
@input="onInput"
@focus="onFocus"
>
2
3
4
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
2
3
4
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
2
3
4
5
6
7
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
2
3
4
5
6
7
# 優先度 C のルール: 推奨
# コンポーネント/インスタンス オプション順序 推奨
コンポーネント/インスタンス オプションは、一貫した順序になるべきです。
これは推奨するコンポーネントオプションの既定の順序です。それらは種類分けされており、プラグインからどこに新たなプロパティを追加するか知ることができます。
グローバルな認識 (コンポーネントを超えた知識が必要)
name
テンプレートのコンパイラオプション (テンプレートのコンパイル方法の変更)
compilerOptions
テンプレートの依存関係 (テンプレートで使用されるアセット)
components
directives
合成 (プロパティをオプションにマージ)
extends
mixins
provide
/inject
インタフェース (コンポーネントへのインタフェース)
inheritAttrs
props
emits
expose
Composition API (Composition API を使用するためのエントリポイント)
setup
ローカルの状態 (ローカル リアクティブ プロパティ)
data
computed
イベント (リアクティブなイベントによって引き起こされたコールバック)
watch
- ライフサイクルイベント (呼び出される順)
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeUnmount
unmounted
errorCaptured
renderTracked
renderTriggered
リアクティブではないプロパティ (リアクティブシステムから独立したインスタンス プロパティ)
methods
レンダリング (コンポーネント出力の宣言的な記述)
template
/render
# 要素の属性の順序 推奨
要素の属性 (コンポーネントを含む) は、一貫した順序になるべきです。
これは推奨するコンポーネントオプションの既定の順序です。それらは種類分けされており、カスタム属性とディレクティブをどこに追加するか知ることができます。
定義 (コンポーネントオプションを提供)
is
リストレンダリング (同じ要素の複数のバリエーションを作成する)
v-for
条件 (要素がレンダリング/表示されているかどうか)
v-if
v-else-if
v-else
v-show
v-cloak
レンダリング修飾子 (要素のレンダリング方法を変更)
v-pre
v-once
グローバルな認識 (コンポーネントを超えた知識が必要)
id
一意の属性 (一意の値を必要とする属性)
ref
key
双方向バインディング (バインディングとイベントの結合)
v-model
その他の属性 (すべての指定されていないバインドされた属性とバインドされていない属性)
イベント (コンポーネントのイベントリスナ)
v-on
コンテンツ (要素のコンテンツを上書きする)
v-html
v-text
# コンポーネント/インスタンス オプションの空行 推奨
特にオプションがスクロールなしでは画面に収まらなくなった場合、複数行に渡るプロパティの間に空行を追加してみてください。
コンポーネントに窮屈さや読みづらさを感じたら、複数行に渡るプロパティの間に空行を追加する事でそれらを簡単に読み流すことができるようになります。Vim など、一部のエディタでは、このような書式を使用するとキーボードで簡単に移動することができます。
良い例
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue() {
// ...
},
inputClasses() {
// ...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// コンポーネントの読み取りや移動が容易であれば、
// 空行がなくても大丈夫です。
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue() {
// ...
},
inputClasses() {
// ...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 単一ファイルコンポーネントのトップレベルの属性の順序 推奨
単一ファイルコンポーネント では、<script>
、<template>
、<style>
タグを一貫した順序にし、 <style>
は他の2つのうち少なくとも1つで常に必要となるため、順序を最後にするべきです。
悪い例
<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
2
3
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
2
3
4
5
6
7
8
9
良い例
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
2
3
4
5
6
7
8
9
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
2
3
4
5
6
7
8
9
# 優先度 D のルール: 注意して使用
# scoped
付きの要素セレクタ 注意して使用
scoped
付きの要素セレクタは避けるべきです。
たくさんの要素セレクタは低速になるため、 scoped
スタイルでは要素セレクタよりもクラスセレクタを優先してください。
詳細な説明
スタイルの範囲を決めるために、 Vue は data-v-f3f3eg9
のような一意の属性をコンポーネントの要素に追加します。
問題は、たくさんの要素属性セレクタ(例: button[data-v-f3f3eg9]
)がクラス属性セレクタ(例: .btn-close[data-v-f3f3eg9]
)よりもかなり遅くなるため、可能な限りクラスセレクタを優先すべきだということです。
悪い例
<template>
<button>×</button>
</template>
<style scoped>
button {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
良い例
<template>
<button class="btn btn-close">×</button>
</template>
<style scoped>
.btn-close {
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
# 暗黙的な親子間のやりとり 注意して使用
親子コンポーネントのやりとりには、 this.$parent
や プロパティの変更ではなく プロパティとイベントを優先するべきです。
理想的な Vue アプリケーションは props down, events up です。 この規則に従うことで、コンポーネントはより理解しやすくなります。しかし、プロパティの変更と this.$parent
が、すでに深く結合している 2 つのコンポーネントを単純化できるエッジケースも存在します。
問題は、これらのパターンが利便性をもたらす 単純な ケースも多くあるということです。 注意:単純さ(状態の流れを理解できること)と短期的な利便性(より少ないコードを書くこと)の交換に誘惑されないようにしましょう。
悪い例
app.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: '<input v-model="todo.text">'
})
2
3
4
5
6
7
8
9
10
app.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
methods: {
removeTodo() {
this.$parent.todos = this.$parent.todos.filter(todo => todo.id !== vm.todo.id)
}
},
template: `
<span>
{{ todo.text }}
<button @click="removeTodo">
×
</button>
</span>
`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
良い例
app.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<input
:value="todo.text"
@input="$emit('input', $event.target.value)"
>
`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<span>
{{ todo.text }}
<button @click="$emit('delete')">
×
</button>
</span>
`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Flux以外の状態管理 注意して使用
グローバルな状態管理には、 this.$root
やグローバルイベントバスではなく Vuex (opens new window) を優先するべきです。
this.$root
での状態管理やグローバルイベントバスは非常に単純なケースでは便利ですが、ほとんどのアプリケーションには適していません。
Vuex は Vue 公式の Flux ライクな実装 で、 状態を管理するための中心的な場所の提供だけでなく、状態の変更を整理、追跡、およびデバッグするためのツールも提供します。 Vuex は、 Vue のエコシステムにうまく統合されています (Vue DevTools の完全なサポートも含みます)。
悪い例
// main.js
import { createApp } from 'vue'
import mitt from 'mitt'
const app = createApp({
data() {
return {
todos: [],
emitter: mitt()
}
},
created() {
this.emitter.on('remove-todo', this.removeTodo)
},
methods: {
removeTodo(todo) {
const todoIdToRemove = todo.id
this.todos = this.todos.filter(todo => todo.id !== todoIdToRemove)
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
良い例
// store/modules/todos.js
export default {
state: {
list: []
},
mutations: {
REMOVE_TODO (state, todoId) {
state.list = state.list.filter(todo => todo.id !== todoId)
}
},
actions: {
removeTodo ({ commit, state }, todo) {
commit('REMOVE_TODO', todo.id)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- TodoItem.vue -->
<template>
<span>
{{ todo.text }}
<button @click="removeTodo(todo)">
X
</button>
</span>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: {
todo: {
type: Object,
required: true
}
},
methods: mapActions(['removeTodo'])
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24