本文へジャンプ

優先度B: 強く推奨

これらのルールは、ほとんどのプロジェクトで可読性や開発者の使い勝手を向上させることが分かっています。これらのルールに違反した場合でも、あなたのコードは動作しますが、違反はごく少数で十分に正当な理由がなければいけません。

コンポーネントのファイル

ファイルを結合してくれるビルドシステムがあるときは、各コンポーネントはそれぞれ別のファイルにするべきです。

そうすれば、コンポーネントを編集したり、使い方を確認したりするときに、より素早く見つけることができるようになります。

悪い例

js
app.component('TodoList', {
  // ...
})

app.component('TodoItem', {
  // ...
})

良い例

components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue

単一ファイルコンポーネントのファイル名の形式

単一ファイルコンポーネント のファイル名は、すべてパスカルケース (PascalCase) にするか、すべてケバブケース (kebab-case) にするべきです。

パスカルケース (PascalCase) は、JS(X) やテンプレートでの中でコンポーネントの参照する方法と一致しているため、コードエディターでの自動補完に最も適しています。しかし、大文字と小文字が混ざったファイル名は、大文字と小文字を区別しないファイルシステムで問題を引き起こすことがあります。そのため、ケバブケース (kebab-case) も完全に許容されます。

悪い例

components/
|- mycomponent.vue
components/
|- myComponent.vue

良い例

components/
|- MyComponent.vue
components/
|- my-component.vue

基底コンポーネントの名前

アプリ特有のスタイルやルールを適用する基底コンポーネント (またはプレゼンテーションコンポーネント: Presentation Components、ダムコンポーネント: Dumb Components、純粋コンポーネント: Pure Components とも) は、すべて BaseAppV などの特定のプレフィックスで始まる必要があります。

詳しい説明

これらのコンポーネントは、あなたのアプリに一貫したスタイルやふるまいをもたせる基礎として位置づけられます。これらは、おそらく以下のものだけを含むでしょう:

  • HTML 要素
  • 別の基底コンポーネント
  • サードパーティー製の UI コンポーネント

しかし、それらにはグローバルな状態(例:Piniaからのストア)は含まれません

これらの名前には、ラップする要素の名前が含まれることが多いです (例: BaseButtonBaseTable など)。ただし、特定の目的のための要素が存在しない場合(例: BaseIcon)はこの限りではありません。もし、より具体的なコンテキストで同様のコンポーネントを作成する場合には、ほとんどの場合にこれらのコンポーネントを使うことになるでしょう。(例えば、 BaseButtonButtonSubmit で使用されるかもしれません)。

このルールの長所はいくつかあります:

  • エディターでアルファベット順に並べられた時に、アプリの基本コンポーネントはすべて一緒にリストされ、識別しやすくなります。

  • コンポーネントの名前は常に複数単語にするべきなので、このルールによって、シンプルなコンポーネントラッパーに任意のプレフィックスを選ばなければならない(例:MyButtonVueButton)ということがなくなります。

  • これらのコンポーネントは頻繁に使用されるので、あらゆる場所で import するのではなく、単純にグローバル化してしまいたいと思うかもしれません。プレフィックスを利用すれば、Webpack で以下ができるようになります:

    js
    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)
    })

悪い例

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

良い例

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

密結合コンポーネントの名前

親コンポーネントと密結合した子コンポーネントには、親コンポーネントの名前をプレフィックスとして含むべきです。

あるコンポーネントが、単一の親コンポーネントとの関係でのみ意味を持つ場合、その関係性はその名前からもわかるようにすべきです。エディターは通常、ファイルをアルファベット順に整理するため、関連するファイルを隣り合わせにすることもできます。

詳しい説明

この問題を、子コンポーネントを親コンポーネントと同じ名前のディレクトリーに入れ子にすることで解決したいと思うかもしれません。例えば:

components/
|- TodoList/
   |- Item/
      |- index.vue
      |- Button.vue
   |- index.vue

または:

components/
|- TodoList/
   |- Item/
      |- Button.vue
   |- Item.vue
|- TodoList.vue

これは推奨されません。以下のような結果を生むからです:

  • 同じような名前のファイルがたくさんできてしまい、コードエディター上で素早くファイルを切り替えるのが難しくなります。
  • ネストしたサブディレクトリーがたくさんできてしまい、エディターのサイドバーでコンポーネントを参照するのに時間がかかるようになります。

悪い例

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

良い例

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

コンポーネント名における単語の順番

コンポーネント名は、最高レベルの(たいていは最も一般的な)単語から始めて、説明的な修飾語で終わるべきです。

詳しい説明

あなたは疑問に思うかもしれません:

"なぜコンポーネント名に自然な言語でないものを使うように強制するのですか?"

自然な英語では、形容詞やその他の記述子は一般的に名詞の前に置かれ、そうでない場合にはコネクターワードが必要になります。例えば:

  • Coffee with milk
  • Soup of the day
  • Visitor to the museum

必要であれば、コンポーネント名にこれらのコネクターワードを含めてもかまいませんが、やはり順番が重要です。

また、何を「最高レベル」として尊重するかは、アプリの文脈に依存することに注意してください。例えば、検索フォームを持つアプリを想像してください。このようなコンポーネントが含まれるかもしれません:

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

お気づきのように、どのコンポーネントが検索に特化しているかを確認するのはかなり困難です。では、ルールに従ってコンポーネントの名前を変えてみましょう。

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue

一般的にエディターではファイルはアルファベット順に並ぶので、コンポーネント間のあらゆる重要な関連性はひと目ではっきりと分かります。

この問題を解決するために、すべての検索コンポーネントを「search」ディレクトリーの下に入れ、すべての設定コンポーネントを「settings」ディレクトリーの下に入れるという方法を取りたくなることがあります。この方法は、以下の理由から、非常に大規模なアプリ(例: 100 以上のコンポーネント)でのみ検討することをお勧めします。

  • 一般的に、入れ子になったサブディレクトリーを移動するのは、単一の components ディレクトリをスクロールするよりも時間がかかります。
  • 名前の衝突(例: 複数の ButtonDelete.vue コンポーネント)により、コードエディターで特定のコンポーネントに素早く移動することが難しくなります。
  • 移動したコンポーネントへの相対参照を更新するには、検索と置換だけでは不十分な場合が多いため、リファクタリングはより困難になります。

悪い例

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

良い例

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

自己終了形式のコンポーネント

コンテンツを持たないコンポーネントは、単一ファイルコンポーネント、文字列テンプレート、および JSX では自己閉鎖されるべきですが、DOM 内テンプレート内ではそうしてはいけません。

自己終了形式のコンポーネントは、単に中身を持たないだけでなく、中身を持たないことを意図したことだとはっきりと表現します。これは、本の中にある白紙のページと、「このページは意図的に白紙のままにしてあります」と書かれたページとの違いです。また、不要な閉じタグがなくなるため、コードもすっきりします。

残念ながら、HTML はカスタム要素の自己終了形式を許可しているのは、公式の「空」要素だけです。これが、Vue のテンプレートコンパイラーが DOM よりも先にテンプレートにアクセスして、その後 DOM の仕様に準拠した HTML を出力することができる場合にだけこの方策を使うことができる理由です。

悪い例

template
<!-- 単一ファイルコンポーネント、文字列テンプレート、JSX の中 -->
<MyComponent></MyComponent>
template
<!-- DOM 内テンプレートの中 -->
<my-component/>

良い例

template
<!-- 単一ファイルコンポーネント、文字列テンプレート、JSX の中 -->
<MyComponent/>
template
<!-- DOM 内テンプレートの中 -->
<my-component></my-component>

テンプレート内でのコンポーネント名の形式

ほとんどのプロジェクトにおいて、単一ファイルコンポーネントと文字列テンプレートの中では、コンポーネント名は常にパスカルケース(PascalCase)になるべきです。しかし、 DOM 内テンプレートの中ではケバブケース(kebab-case)です。

パスカルケース(PascalCase)はケバブケース(kebab-case)に比べ、いくつかの利点があります:

  • パスカルケースは JavaScript でも使用されるため、エディターはテンプレート内のコンポーネント名をオートコンプリートすることができます。
  • <MyComponent> <my-component> よりも一単語の HTML 要素との見分けがつきやすいです。なぜなら、ハイフン 1 文字だけの違いではなく 2 文字(2 つの大文字) の違いがあるからです。
  • もし、テンプレート内で、Vue 以外のカスタム要素(例: Web コンポーネントなど)を使っていたとしても、パスカルケースは Vue コンポーネントがはっきりと目立つことを保証します。

残念ですか、HTML は大文字と小文字の区別をしないため、DOM 内テンプレートではまだケバブケースを使用する必要があります。

ただし、もしあなたが既にケバブケースを大量に使っているのなら、HTML の規約との整合性や、すべてのプロジェクトで同じケーシングを使用できることが、上記の利点よりも重要な場合があることに注意してください。このような状況では、 あらゆる場所でkebab-caseを使うことも許容されます。

悪い例

template
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<mycomponent/>
template
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<myComponent/>
template
<!-- DOM 内テンプレートの中 -->
<MyComponent></MyComponent>

良い例

template
<!-- 単一ファイルコンポーネント、文字列テンプレートの中 -->
<MyComponent/>
template
<!-- DOM 内テンプレートの中 -->
<my-component></my-component>

または

template
<!-- どこでも -->
<my-component></my-component>

JS/JSX 内でのコンポーネント名の形式

JS/JSX内でのコンポーネント名は常にパスカルケース(PascalCase)にするべきです。ただし、app.component で登録したグローバルコンポーネントしか使わないような単純なアプリケーションでは、ケバブケース(kebab-case)を含む文字列になるかもしれません。

詳しい説明

JavaScript では、クラスやプロトタイプのコンストラクターなど、基本的に個別のインスタンスを持つことができるすべてのもののためにパスカルケースを利用します。Vue のコンポーネントもインスタンスを持つので、パスカルケースを使用するのは理にかなっています。さらに、JSX(およびテンプレート)内でパスカルケースを使用すると、コードを読む人がコンポーネントと HTML 要素をより簡単に区別することができます。

しかし、app.component によるグローバルなコンポーネント定義のみを使用するアプリケーションでは、代わりにケバブケース(kebab-case)を使用することをお勧めします。理由は以下の通りです。

  • JavaScript でグローバルコンポーネントを参照することは稀であり、JavaScript の規約に従うことはあまり意味がありません。
  • そのようなアプリケーションはたくさんの DOM 内テンプレートをもつのが常ですが、 そこでは ケバブケースを必ず使う必要があります.

悪い例

js
app.component('myComponent', {
  // ...
})
js
import myComponent from './MyComponent.vue'
js
export default {
  name: 'myComponent'
  // ...
}
js
export default {
  name: 'my-component'
  // ...
}

良い例

js
app.component('MyComponent', {
  // ...
})
js
app.component('my-component', {
  // ...
})
js
import MyComponent from './MyComponent.vue'
js
export default {
  name: 'MyComponent'
  // ...
}

完全な単語によるコンポーネント名

コンポーネント名には、略語よりも完全な単語を使うべきです。

長い名前によってもたらされる明快さは非常に貴重ですし、それをタイプする労力はエディターの自動補完によってとても小さくなります。特に、一般的でない略語は常に避けるべきです。

悪い例

components/
|- SdSettings.vue
|- UProfOpts.vue

良い例

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

props 名の型式

props 名は、定義の時は常にキャメルケース(camelCase)を使用する必要があります。DOM 内テンプレート内で使用する場合、props はケバブケース(kebab-case)を使用する必要があります。単一ファイルコンポーネントのテンプレートと JSX では、ケバブケースまたはキャメルケースのどちらかの props を使用できます。ケーシングは一貫している必要があります - キャメルケースの props を使用することにした場合、そのアプリケーション内ではケバブケースを使用しないでください

悪い例

js
props: {
  'greeting-text': String
}
js
const props = defineProps({
  'greeting-text': String
})
template
// DOM 内テンプレートの場合
<welcome-message greetingText="hi"></welcome-message>

良い例

js
props: {
  greetingText: String
}
js
const props = defineProps({
  greetingText: String
})
template
// SFC の場合 - ケーシングはプロジェクト全体で統一してください
// どちらを使っても構いませんが、2つの異なるケーシングスタイルを混在させることはお勧めしません
<WelcomeMessage greeting-text="hi"/>
// もしくは
<WelcomeMessage greetingText="hi"/>
template
// DOM 内テンプレートの場合
<welcome-message greeting-text="hi"></welcome-message>

複数の属性をもつ要素

複数の属性をもつ要素は、1 行に 1 要素ずつ、複数の行にわたって書くべきです。

JavaScript では、複数のプロパティをもつ要素を複数の行に分けて書くことはよい慣習だと広く考えられています。なぜなら、その方がより読みやすいからです。Vue のテンプレートや JSX も同じように考えることがふさわしいです。

悪い例

template
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
template
<MyComponent foo="a" bar="b" baz="c"/>

良い例

template
<img
  src="https://vuejs.org/images/logo.png"
  alt="Vue Logo"
>
template
<MyComponent
  foo="a"
  bar="b"
  baz="c"
/>

テンプレート内での単純な式

複雑な式は算出プロパティかメソッドにリファクタリングして、コンポーネントのテンプレートには単純な式だけを含むようにするべきです。

テンプレート内に複雑な式があると、テンプレートが宣言的ではなくなります。私たちはどのようにその値を算出するかではなく、何が表示されるべきかを記述するように努力するべきです。また、算出プロパティやメソッドによってコードが再利用できるようになります。

悪い例

template
{{
  fullName.split(' ').map((word) => {
    return word[0].toUpperCase() + word.slice(1)
  }).join(' ')
}}

良い例

template
<!-- テンプレート内 -->
{{ normalizedFullName }}
js
// 複雑な式を算出プロパティに移動
computed: {
  normalizedFullName() {
    return this.fullName.split(' ')
      .map(word => word[0].toUpperCase() + word.slice(1))
      .join(' ')
  }
}
js
// 複雑な式を算出プロパティに移動
const normalizedFullName = computed(() =>
  fullName.value
    .split(' ')
    .map((word) => word[0].toUpperCase() + word.slice(1))
    .join(' ')
)

単純な算出プロパティ

複雑な算出プロパティは、できる限りたくさんの単純なプロパティに分割するべきです。

詳しい説明

単純な、よい名前を持つ算出プロパティは:

  • テストしやすい

    それぞれの算出プロパティが、依存がとても少ないごく単純な式だけを含む場合、それが正しく動くことを確認するテストを書くのがより簡単になります。

  • 読みやすい

    算出プロパティを単純にするということは、たとえそれが再利用可能ではなかったとしても、それぞれに分かりやすい名前をつけることになります。それによって、他の開発者(そして未来のあなた)が、注意を払うべきコードに集中し、何が起きているかを把握することがより簡単になります。

  • 要求の変更を受け入れやすい

    名前をつけることができる値は何でも、ビューでも役に立つ可能性があります。例えば、いくら割引になっているかをユーザーに知らせるメッセージを表示することに決めたとします。 また、消費税も計算して、最終的な価格の一部としてではなく、別々に表示することにします。

    小さく焦点が当てられた算出プロパティは、どのように情報が使われるかの決めつけをより少なくし、少しのリファクタリングで要求の変更を受け入れられます。

悪い例

js
computed: {
  price() {
    const basePrice = this.manufactureCost / (1 - this.profitMargin)
    return (
      basePrice -
      basePrice * (this.discountPercent || 0)
    )
  }
}
js
const price = computed(() => {
  const basePrice = manufactureCost.value / (1 - profitMargin.value)
  return basePrice - basePrice * (discountPercent.value || 0)
})

良い例

js
computed: {
  basePrice() {
    return this.manufactureCost / (1 - this.profitMargin)
  },

  discount() {
    return this.basePrice * (this.discountPercent || 0)
  },

  finalPrice() {
    return this.basePrice - this.discount
  }
}
js
const basePrice = computed(
  () => manufactureCost.value / (1 - profitMargin.value)
)

const discount = computed(
  () => basePrice.value * (discountPercent.value || 0)
)

const finalPrice = computed(() => basePrice.value - discount.value)

引用符付きの属性値

空ではない HTML 属性の値は常に引用符(シングルコーテーションかダブルコーテーション、 JS の中で使われていない方)でくくるべきです。

HTML では、空白を含まない属性値は引用符でくくらなくてもよいことになっていますが、そのせいで空白の使用を 避けてしまい 属性値が読みづらくなりがちです。

悪い例

template
<input type=text>
template
<AppSidebar :style={width:sidebarWidth+'px'}>

良い例

template
<input type="text">
template
<AppSidebar :style="{ width: sidebarWidth + 'px' }">

ディレクティブの短縮記法

ディレクティブの短縮記法 (v-bind: に対する :v-on: に対する @v-slot に対する #)は、常に使うか、まったく使わないかのどちらかにするべきです。

悪い例

template
<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>
template
<input
  v-on:input="onInput"
  @focus="onFocus"
>
template
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>

良い例

template
<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>
template
<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>
template
<input
  @input="onInput"
  @focus="onFocus"
>
template
<input
  v-on:input="onInput"
  v-on:focus="onFocus"
>
template
<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>
template
<template #header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>
優先度B: 強く推奨が読み込まれました