Help us understand the problem. What is going on with this article?

Vue.js スタイルガイド早見表 🎄✨

Vue.jsの公式スタイルガイドは長い!!!
サクッっと要点だけまとまったガイドが欲しい。そんなアナタのために書きました。

この記事はVue.jsのスタイルガイドを簡潔にまとめたものです。
( ※部分的に補足を加えたりしています。 )

対象となる人物像: 一度目を通したことがある人 or 時間の無い人 or ザックリでいいから知りたい人

ルールカテゴリ

Vueのスタイルガイドは4つのカテゴリに分けられています。
A > B > C > D の順で優先度 (強制力) が強いです。

A. 必須 B. 強く推奨 C. 推奨 D. 注意(危険)
エラー防止 読みやすさ向上 一貫性の確保 潜在的に危険、
予期せぬ副作用を起こす可能性を事前に回避

📎 [A. 必須] 一覧を見る
📎 [B. 強く推奨] 一覧を見る
📎 [C. 推奨] 一覧を見る
📎 [D. 注意] 一覧を見る

A. 必須

複数単語コンポーネント名

コンポーネント名は複数単語にする。
※将来定義されるHTML要素との衝突を防止するため。

😊 Good

<todo-item />

😰 Bad

<todo />


コンポーネントのデータ

dataプロパティを使用する際は、オブジェクトを返す関数にする。

😊 Good

export default {
  data () {
    return {
      foo: 'bar'
    }
  }
}

😰 Bad

export default {
  data: {
    foo: 'bar'
  }
}


プロパティの定義

propsプロパティの定義は、できる限り詳細にする。
📎 Vue.jsのpropsカスタムバリデーターを使った堅牢なコンポーネント作成

😊 Good

props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}

😰 Bad

props: ['status']


キー付き v-for

v-forにはkeyを付与する。( パフォーマンス向上 )
keyはVirtualDOMのdiffから実際のDOMに反映させるときに、最小限の変更するために使われる。
コストをかけずにDOM変化を実行するためにもkeyを付与するようにする。
📎 React.jsの地味だけど重要なkeyについて

😊 Good

<ul>
  <li
    v-for="todo in todos"
    :key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

😰 Bad

<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>


コンポーネントスタイルのスコープ

コンポーネントのスタイルは、下記ルールのどれかを採用する。

  1. scoped属性を使用する
  2. CSS modulesを使用する
  3. BEMのようなCSS設計を採用する

😊 Good ( 1. scoped属性を使用する )

<template>
  <button class="button button-close">X</button>
</template>

<!-- `scoped` を使用 -->
<style scoped>
.button {
  border: none;
  border-radius: 2px;
}
.button-close {
  background-color: red;
}
</style>

😊 Good ( 2. CSS modulesを使用する )

<template>
  <button :class="[$style.button, $style.buttonClose]">X</button>
</template>

<!-- CSS modules を使用 -->
<style module>
.button {
  border: none;
  border-radius: 2px;
}
.buttonClose {
  background-color: red;
}
</style>

😊 Good ( 3. BEMのようなCSS設計を採用する )

<template>
  <button class="c-Button c-Button--close">X</button>
</template>

<!-- BEM の慣例を使用 -->
<style>
.c-Button {
  border: none;
  border-radius: 2px;
}
.c-Button--close {
  background-color: red;
}
</style>

😰 Bad

<template>
  <button class="btn btn-close">X</button>
</template>

<!-- CSSがグローバル汚染 -->
<style>
.btn-close {
  background-color: red;
}
</style>


プライベートなプロパティ名

プラグインやミックスインなどのカスタムプロパティには、$_プレフィックスを付与する。
※コンポーネントのプロパティとの衝突を避けるため

😊 Good

mixins/hoge.js
var myGreatMixin = {
  // ...
  methods: {
    $_myGreatMixin_update: function() {
      // ...
    }
  }
}

😰 Bad

mixins/hoge.js
var myGreatMixin = {
  // ...
  methods: {
    update: function() {
      // ...
    }
  }
}

B. 強く推奨

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

各コンポーネントはそれぞれ別のファイルに書くようにする。
※ コンポーネントが分かれていることで対象コンポーネントを探しやすくなる。可読性があがる。

😊 Good

// ファイルがコンポーネントごとに分けられている

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

😰 Bad

// ひとつのファイルにコンポーネントを書きまくる

Vue.component('TodoList', {
  // ...
})
Vue.component('TodoItem', {
  // ...
})


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

基底コンポーネントは、すべてBaseAppV などの固有のプレフィックスで始める。

😊 Good

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

😰 Bad

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


単一インスタンスのコンポーネント名

ページごとに1回しか使われないコンポーネントは、Theというプレフィックスで始める。
※1つしか存在しえないことを示すため

😊 Good

components/
|- TheHeading.vue
|- TheSidebar.vue
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

😰 Bad

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


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

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

😊 Good

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

😰 Bad

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


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

コンポーネント名は一般的な単語から始まり、説明的な修飾語で終わるようにする。

例: 検索フォームのあるアプリケーション

😊 Good

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

※ 100以上のコンポーネントがあるような場合のみSearchディレクトリの下にネストするのを推奨。

😰 Bad

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


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

単一ファイルコンポーネント 、文字列テンプレート、およびJSXの中では自己終了形式で書くようにする。
※DOMテンプレート内は除く

😊 Good

<MyComponent />

😰 Bad

<MyComponent></MyComponent>


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

単一ファイルコンポーネントと文字列テンプレート中のカスタムタグは、常にパスカルケース(PascalCase)にする。
※ DOM テンプレートの中ではケバブケース(kebab-case)であるべき。

HTMLは大文字と小文字を区別しないので、DOM テンプレートの中ではまだケバブケースを使う。

😊 Good

<MyComponent />

😰 Bad

<mycomponent />
<myComponent />


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

JS/JSX内でのコンポーネント名は、パスカルケース(PascalCase)にする。

😊 Good

Vue.component('MyComponent', {
  // ...
})
import MyComponent from './MyComponent.vue'
export default {
  name: 'MyComponent',
  // ...
}

😰 Bad

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


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

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

😊 Good

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

😰 Bad

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


プロパティ名の型式

プロパティ名は、定義の時はキャメルケース(camelCase)、
HTML(テンプレート)やJSXで使う際はケバブケース(kebab-case)にする。

😊 Good

props: {
  greetingText: String
}
<WelcomeMessage greeting-text="hi"/>

😰 Bad

props: {
  'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>


複数の属性をもつ要素

複数の属性をもつ要素は、1行に1要素ずつ、複数の行に渡って書くようにする。

😊 Good

<img
  src="https://vuejs.org/images/logo.png"
  alt="Vue Logo"
>

😰 Bad

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">


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

複雑な式は、算出プロパティかメソッドに書くようにする。
コンポーネントのテンプレートには単純な式だけを含むようにする。
※テンプレートには、何が表示されるかを簡潔に記述する。

😊 Good

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

😰 Bad

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


引用符付きの属性値

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

😊 Good

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

😰 Bad

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


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

ディレクティブの短縮記法: @は、常に使うか、まったく使わないかのどちらかにする。

😊 Good

<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>
<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>
<input
  @input="onInput"
  @focus="onFocus"
>
<input
  v-on:input="onInput"
  v-on:focus="onFocus"
>

😰 Bad

<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>
<input
  v-on:input="onInput"
  @focus="onFocus"
>

C. 推奨

コンポーネント/インスタンス オプション順序

下記の順序を推奨。
※一貫した順序を守ることで、プロパティが探しやすくなる。

 - el
 - name
 - parent
 - functional
 - delimiters
 - comments
 - components
 - directives
 - filters
 - extends
 - mixins
 - inheritAttrs
 - model
 - props/propsData
 - data
 - computed
 - watch
 - ライフサイクルイベント (呼び出される順)
 - methods
 - template/render
 - renderError


要素の属性の順序

下記の順序を推奨。
※一貫した順序を守ることで、カスタム属性とディレクティブが探しやすくなる。

 - is
 - v-for
 - v-if
 - v-else-if
 - v-else
 - v-show
 - v-cloak
 - v-pre
 - v-once
 - id
 - ref
 - key
 - slot
 - v-model
 - その他の属性
 - v-on
 - v-html
 - v-text


コンポーネント/インスタンス オプションの空行

スクロールする程、長くなった場合はプロパティの間に空行を追加する。

😊 Good

props: {
  value: {
    type: String,
    required: true
  },

  focused: {
    type: Boolean,
    default: false
  },

  label: String,
  icon: String
},
computed: {
  formattedValue: function () {
    // ...
  },

  inputClasses: function () {
    // ...
  }
}


単一ファイルコンポーネントのトップレベルの属性の順序

単一ファイルコンポーネントでは、 <template><script><style> の順で書くようにする。

😊 Good

<template></template>
<sctipt></sctipt>
<style></style>

😰 Bad

<template></template>
<style></style>
<sctipt></sctipt>

D. 注意(危険)

keyを使わない v-if / v-if-else / v-else

v-if + v-elseと一緒にkeyを書かないと予期せぬ副作用が生じる。

副作用の例: https://jsfiddle.net/chrisvfritz/bh8fLeds/

keyは必ず書くようにする。

😊 Good

<div v-if="error" key="search-status">
  Error: {{ error }}
</div>
<div v-else key="search-results">
  {{ results }}
</div>

😰 Bad

<div v-if="error">
  Error: {{ error }}
</div>
<div v-else>
  {{ results }}
</div>


scoped付きの要素セレクタ

要素セレクタに直接CSSを書くのはやめましょう。
※CSSを書く際は、クラスセレクタを使うようにする。(パフォーマンス向上)

😊 Good

<style scoped>
.btn-close {
  background-color: red;
}
</style>

😰 Bad

<style scoped>
button {
  background-color: red;
}
</style>


暗黙的な親子間のやりとり

$parent$childrenは使わないようにする。

😊 Good

Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  template: `
    <input
      :value="todo.text"
      @input="$emit('input', $event.target.value)"
    >
  `
})
Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  template: `
    <span>
      {{ todo.text }}
      <button @click="$emit('delete')">
        X
      </button>
    </span>
  `
})

😰 Bad

Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  template: '<input v-model="todo.text">'
})
Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: {
    removeTodo () {
      var vm = this
      vm.$parent.todos = vm.$parent.todos.filter(function (todo) {
        return todo.id !== vm.todo.id
      })
    }
  },
  template: `
    <span>
      {{ todo.text }}
      <button @click="removeTodo">
        X
      </button>
    </span>
  `
})


Flux 以外の状態管理

グローバル状態管理には、this.$rootやグローバルイベントバスよりも、Vuexが推奨される。
Vuexを使うようにする。

😊 Good

// 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)
    }
  }
}
<!-- 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>

😰 Bad

// main.js

new Vue({
  data: {
    todos: []
  },
  created: function () {
    this.$on('remove-todo', this.removeTodo)
  },
  methods: {
    removeTodo: function (todo) {
      var todoIdToRemove = todo.id
      this.todos = this.todos.filter(function (todo) {
        return todo.id !== todoIdToRemove
      })
    }
  }
})


まとめ

  • 全てを遵守する必要は無い
    • 個人的には A > B = D > Cの順で大事かなぁと思いました
  • チームでコーディングルールを採用する上での指標になる

参考 ( special thx )

さいごに

このアドベントカレンダーを通して、多くの人がVue.jsのスタイルガイドを知るきっかけになればと思います。
フライングメリークリスマス!🎄✨

mi_upto
お肉とお寿司とわんこが好きです。 twitter : https://twitter.com/mi_upto
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away