Vue.jsのスタイルガイドを読んだ

先日、神戸三宮を中心に関西を盛り上げているVue.jsコミュニティ、**[三宮.vue](https://kobe-sannomiya-vue.connpass.com/)**の勉強会に初めて参加して来ました。  
そこで、**「Vue.jsのスタイルガイドをちゃんと読もう！」**という発表に触発され、改めて自分でも[スタイルガイド](https://jp.vuejs.org/v2/style-guide/)を読んでみました。  

読んでみて、「当たり前ではないか！~~へのつっぱりはいらんですよ！~~」と思うことから、「へーー！そら知らなんだ！」ということもありましたが、どれも**「エラーを防止する」**、**「可読性を上げる」**、**「一貫性を持たせる」**、**「保守性を上げる」**という面で大事です。

実際に[スタイルガイド](https://jp.vuejs.org/v2/style-guide/)を読むのが一番ですが、折角なので優先度Aの必須項目をメモとして書きました。

## [複数単語コンポーネント名を使う](https://jp.vuejs.org/v2/style-guide/#%E8%A4%87%E6%95%B0%E5%8D%98%E8%AA%9E%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E5%90%8D-%E5%BF%85%E9%A0%88)

> ルートの `App` コンポーネントや、Vue が提供する `<transition>` や `<component>` のようなビルトインコンポーネントを除き、コンポーネント名は常に複数単語とするべきです。

```js:example1.vue
Vue.component('todo-item', {
  // ...
})
```

```js:example2.vue
export default {
  name: 'TodoItem',
  // ...
}
```

これは、HTML要素の名前とコンポート名が衝突するのを防ぐのが目的です。  
全ての HTML 要素は 1 単語となっているので、コンポート名として複数単語を使っているとHTMLの要素名と衝突することはないということです。  
例えば `todo` や `Todo` というコンポート名をつけると将来 `todo` というHTML要素が出て来たときに名前が被ってしまうので、コンポート名としては `example1` や `example2` のように `todo-item` や `TodoItem` というような名前にすると良いです。  

## [コンポートのデータは関数でなければならない](https://jp.vuejs.org/v2/style-guide/#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF-%E5%BF%85%E9%A0%88)

> コンポーネントで `data` プロパティを使用する際 (つまり `new Vue` 以外のどこでも)、その値はオブジェクトを返す関数でなければなりません。

例えば、`data` の値がオブジェクトの場合、それはコンポーネントの全てのインスタンスで共有されます。

```js:bad-example.vue
export default {
  data: {
    listTitle: '',
    todos: []
  }
}
```

このように書き、このコンポーネントを再利用しようとした場合、コンポーネントの全てのインスタンスが同じデータオブジェクトを参照するので、1つのリストのタイトルを変えると、他のリストのタイトルも変わってしまいます。

そこで、各コンポーネントのインスタンスには自身のデータだけを管理してもらおうとすると、各インスタンスは一意のデータオブジェクトを生成する必要があります。

```js:good-example1.vue
export default {
  data: function () {
    return {
      listTitle: '',
      todos: []
    }
  }
}
```

もしくは

```js:good-example.vue
export default {
  data () {
    return {
      listTitle: '',
      todos: []
    }
  }
}
```

このように、関数内でオブジェクトを返すと、各インスタンスが一意のデータオブジェクトを生成することができます。(※ ルートで直接オブジェクトを使うのは OK です)



## [プロパティの定義はできる限り詳細とする](https://jp.vuejs.org/v2/style-guide/#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%AE%9A%E7%BE%A9-%E5%BF%85%E9%A0%88)

> コミットされたコード内で、プロパティの定義は常に少なくとも1つのタイプを指定し、できる限り詳細とするべきです。

```js:example.vue
export default {
  props: {
    status: {
      type: String,
      required: true,
    }
  }
}
```

`type` や `required` を明記することで、ミスを防いだり、エラーの原因の特定をし易くするという話ですね。  


## [ v-for に対して常に key を使用する](https://jp.vuejs.org/v2/style-guide/#%E3%82%AD%E3%83%BC%E4%BB%98%E3%81%8D-v-for-%E5%BF%85%E9%A0%88)

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

このように一意のキーを振りましょう。
もし、

```vue:bad-example.vue
<li
    v-for="(item, index) in items"
    :key="index"
 >
```

とすると、キーの値が書き換わった場合に意図しない挙動に繋がる可能性があります。そのため、キーには`good-example`のように一意の`id`を設定するのが基本です。(参考: [Vue.js: v-forで項目インデックスをkey属性にしていいのか](https://qiita.com/FumioNonaka/items/d1d9c9335116426a8316))  


## [v-for と一緒に v-if を使うのを避ける](https://jp.vuejs.org/v2/style-guide/#v-for-%E3%81%A8%E4%B8%80%E7%B7%92%E3%81%AB-v-if-%E3%82%92%E4%BD%BF%E3%81%86%E3%81%AE%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B-%E5%BF%85%E9%A0%88)

```vue:good-example.vue
<ul v-if="shouldShowUsers">
  <li
    v-for="user in users"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
```
`good-example`の場合、v-if をコンテナ要素に置いているので、`shouldShowUsers`が `false` の場合は `v-for` を評価しません。

もしこれを、

```vue:bad-example.vue
<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
```
このように書くと、  
**v-forは v-if よりも優先度が高い**ので、このテンプレートは、

```js
this.users.map(function (user) {
  if (user.isActive) {
    return user.name
  }
})
```

と同様に評価されます。
アクティブユーザーが変更されたかどうかに関わらず、再レンダリングするたびにリスト全体を繰り返し処理する必要があります。

## [コンポーネントをスコープにする](https://jp.vuejs.org/v2/style-guide/#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97-%E5%BF%85%E9%A0%88)

> アプリケーションにとって、トップレベルの `App` コンポーネントとレイアウトコンポーネント内のスタイルはグローバルかもしれませんが、他のすべてのコンポーネントは常にスコープされているべきです。

```vue:good-example.vue
<template>
  <button class="button button-close">X</button>
</template>

<style scoped>
.button {
  border: none;
  border-radius: 2px;
}

.button-close {
  background-color: red;
}
</style>
```

上の例は `style` に `scoped` 属性を使用しています。  

これは、スタイルの汚染を防ごうということです。  
`scoped` 属性を使わなくても、BEMなどを用いることで汚染を防ぐ方が良い場合もあります。  


## [プライベートなプロパティ名を使う](https://jp.vuejs.org/v2/style-guide/#%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%81%AA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D-%E5%BF%85%E9%A0%88)

> プライベートな関数に外部からアクセスできないようにするために、モジュールスコープを使用してください。それが難しい場合は、プラグインやミックスインなどのプライベートなカスタムプロパティには常に`$_` プレフィックスを使用してください。さらに、他の著者によるコードとの衝突を避けるため、名前付きのスコープを含めてください (例 `$_yourPluginName_` )。

```js:good-example.vue
var myGreatMixin = {
  // ...
  methods: {
    publicMethod() {
      // ...
      myPrivateFunction()
    }
  }
}

function myPrivateFunction() {
  // ...
}

export default myGreatMixin
```

---

以上が[Vue.jsスタイルガイド](https://jp.vuejs.org/v2/style-guide/)の中で、優先順位が最も高い必須7項目です。

これまでちゃんと読んだことがなかったのですが、読んだ方が良いですね。  
最初に書いたように、当たり前だと思うことも知らなかったことも書いてありますが、使う上での共通認識として理解しておくべきだと感じました。  

本記事で紹介している優先度Aの必須7項目以外にも優先度B（強く推奨）、C（推奨）、D（使用注意）の項目があるので、是非[スタイルガイド](https://jp.vuejs.org/v2/style-guide/)に目を通してみてください。

そう言えば、Nuxtを使ったときにESLintに `Require self-closing on Vue.js custom components` や `”:value” should go before “〜”`のように言われることがありました。これらも、優先度Bの項目の[自己終了形式のコンポーネント](https://jp.vuejs.org/v2/style-guide/#%E8%87%AA%E5%B7%B1%E7%B5%82%E4%BA%86%E5%BD%A2%E5%BC%8F%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88-%E5%BC%B7%E3%81%8F%E6%8E%A8%E5%A5%A8) や[コンポーネント名における単語の順番](https://jp.vuejs.org/v2/style-guide/#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E5%90%8D%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E5%8D%98%E8%AA%9E%E3%81%AE%E9%A0%86%E7%95%AA-%E5%BC%B7%E3%81%8F%E6%8E%A8%E5%A5%A8) に則ったルールに基づいているんですね。  

知らなんだ。

---

**[三宮.vue](https://kobe-sannomiya-vue.connpass.com/)** 楽しかったので、関西にいる方は是非参加してみてください！


## 参考

- [Vue.js スタイルガイド](https://jp.vuejs.org/v2/style-guide/)
- [Vue.js: v-forで項目インデックスをkey属性にしていいのか](https://qiita.com/FumioNonaka/items/d1d9c9335116426a8316)