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

Vue開発者のためのVue.jsベストプラクティス集15選

はじめに

みなさん、Vue使ってますかー!・・・・・・・(へんじがない。ただのしかばねのようだ。)

私は毎日使ってます。が、正しい使い方というのがいまいちわかっていません。ということで、ネットで拾ってきた情報を元にVueで開発する上でのベストプラクティス集15選をまとめてみました。

「ふんふんそうだよね〜」といったものから「えー!?そうなのー!?」となるものまで集めています。皆さんの開発の手助けになる情報が入っていると幸いです。

また、間違っている内容やさらに良い方法等あれば、コメント欄にて(優しく)ご指摘いただけるととても嬉しいです。

1. v-for内では必ず:keyを使う

v-forディレクティブでkey属性を使うと、データを操作するのに役立ちます。さらに、Vueがコンポーネントの状態を追跡し、それぞれのエレメントに対し継続的に参照ができるようになります。 特に、アニメーションやVueトランジションでkeyが活躍するので興味ある方はリンク先をみてみてください。

keyがない場合、VueはDOMを効率よく生成する「だけ」になります。さらにv-for内のエレメントが順番通り表示されないといった、振る舞いが予測できなくなる等の弊害が発生します。

上記の理由から、v-forを使うときは必ずkeyもセットしましょう。それぞれのエレメントに固有のキーを割り振ることで、VueアプリがDOM操作をどの様に行うか、予想が簡単になります。

<!-- 悪い例 -->
<div v-for='animal in animals' />

<!-- 良い例 -->
<div v-for='animal in animals' :key='animal.id' />

2. イベント名にはケバブケースを使う

カスタムイベントをemitさせる際のイベント名は、ケバブケース(kebab-case)が最適です。親コンポーネントで同じ構文のイベントを動かすからです。

イベント名はVueが自動的に小文字に変換してくれますが、コンポーネント間で一貫性を保ち読みやすいコードにするために、ケバブケースを使うことをお勧めします。

// 子コンポーネント
this.$emit('close-window')
<!-- 親コンポーネント -->
<popup-window v-on:close-window='handleEvent()' />

3. Propsの宣言にはキャメルケース、テンプレートにはケバブケースを使う

JavaScriptではキャメルケース(camelCase)が基本的ですが、HTMLではケバブケース(Kebab case)を使います。Vueでもこのルールに従って命名しましょう。

Vueはケバブケースとキャメルケースを自動で変換してくれるので、宣言する時以外はあまり気にする必要はありません。

<!-- 悪い例 -->
<PopupWindow greetingText='good morning!' /> 
props: { 'greeting-text': String }

<!-- 良い例 -->
<PopupWindow greeting-text='good morning!' /> 
props: { greetingText: String }

4. コンポーネントオプションの順番はスタイルガイドに従う

コンポーネントのオプションは同じ順番で定義しましょう。探す時間が省けたり、新しくコンポーネントを作る際に便利です。

例えば、イベントの順番は下記のように定義すると良しとされています。
watch
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed

公式ドキュメント(コンポーネント/インスタンス オプション順序) に詳しくのっているので、ぜひ一読してみてください。

5. データプロパティでは必ずFunctionを返す

コンポーネントでデータを宣言する際、データプロパティには必ずfunctionをreturnするようにしましょう。returnしない場合、オブジェクトを返すことになり、データがすべてのコンポーネントのインスタンスで共有されてしまいます。

再利用可能で固有のオブジェクトを返すコンポーネントを作る場合がほとんどだと思いますが、データオブジェクトをfunction内でreturnすることで実現することができます。

// 悪い例 
data: {
  name: 'Momoko Toyota',
  hobbies: []
}

// 良い例
data () {
  return {
    name: 'Momoko Toyota',
    hobbies: []
  }
}

6. v-forv-ifは同じエレメントで使わない

エレメントを何らかの条件でフィルターする時、v-ifv-forを一緒に使いたくなりますよね。

<div v-for='book in books' v-if='book.price < 1500'>

v-forディレクティブはv-ifよりも高い優先度です。言いかえると、内部でエレメントをループしてから、v-ifで条件をチェックしています。例えば下記コードの様な感じです。

this.books.map(function (book) {
  if (books.price < 1500) {
    return book
  }
})

ということは、booksから数個のエレメントを描画したい時、books配列全体を繰り返し処理する必要があるということです。あまりいい動きではありません。

解決案はcomputedプロパティを使うことです。下記の様に置き換えられます。

<div v-for='book in reasonableBooks'>
computed: {
  reasonableBooks: () => {
    return this.books.filter(function (book) {
      return books.price < 1500
    })
  }
}

この方法が良い理由として、下記が挙げられます。

  • 繰り返し処理を全てのエレメントに対し行わないので、より効率良く描画できる
  • 依存関係が変更された時に、フィルターされたリストのみ再評価される
  • テンプレートのコンポーネントロジックを分割できる。結果的に読みやすいコンポーネントになる

7. Propsのバリデートをしっかりと定義する

アプリが大きくなればなるほど、propsで使われているフォーマットやデータタイプ、その他の規約を忘れてしまいやすいと思います。特に大規模な開発チームに属している場合、自分の作ったコンポーネントをどの様に使えば良いか、チームメンバーはひと目ではわかりません。

チームメンバーがあなたが作ったコンポーネントのpropsのフォーマットなどを確認する時間を少しでも省く為に、propsのバリデーションはしっかりと指定しましょう。

詳しくは公式サイト(プロパティのバリデーション) がお勧めです。

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

8. コンポーネント名にはパスカルケースかケバブケースを使う

コンポーネント名にはパスカルケース(PascalCase)か(kebab-case)を使うという規約があります。どちらを使っても問題ありませんが、どちらか一方にしぼりましょう。

ほとんどのIDEのオートコンプリート機能がサポートしているため、パスカルケースがお勧めです。

# 悪い例

mycomponent.vue
myComponent.vue
Mycomponent.vue

# 良い例

MyComponent.vue
my-component.vue

9. 基底コンポーネント名にはプレフィックスをつける

ボタンやアイコンなど、アプリ内の複数のコンポーネントで使用するコンポーネントを基底コンポーネント(Base components) と呼びます。

Vueスタイルガイド によると、下記の3つのコンポーネントのみ含んでいるものが基底コンポーネントです。

  • HTMLエレメント
  • 他の基底コンポーネント
  • 第3者のUIコンポーネント

これらのコンポーネントの命名規則は、BaseV、またはApp等、特定のプレフィックスをつけることです。どれを使っても大丈夫ですが、どれか1つに絞って使う様にしましょう。

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

components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue

components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

この命名規約に従うと、ファイルシステムで基底コンポーネントをアルファベット順にグループ化してくれます。また、webpackのインポート関数を使うことによって、この命名規則のパターンにマッチするコンポーネントを探し、Vueプロジェクトのグローバルに自動的にインポートしてくれます。

10. destroy()$offでイベントリスナを削除する

$onを使ってイベントを監視した場合、destroyed()$offを使いリスナを削除するのを忘れない様にしましょう。メモリーリークを防ぐことができます。

11. 一度だけ宣言・使用されるコンポーネント名にはプレフィックス「The」をつける

基底コンポーネントと似たものに、単一インスタンスコンポーネントがあります。すべてのページで一度だけ使用され、propsを使用していないコンポーネントです。例えば、ヘッダーやサイドバー、フッターなどです。

一つのアクティブなインスタンスしか存在しえないことを示す為に、単一インスタンスコンポーネント名にはプレフィックス「The」をつけましょう。

単一インスタンスのコンポーネント名についてはこちら

components/
|- TheHeading.vue
|- TheSidebar.vue
|- TheFooter.vue

12. ミックスインプロパティには$_を使う

ミックスイン (mixin)は、繰り返し利用されるコードを1つのブロックに入れて何度も再利用可能にするための方法です。しかし、幾つか問題があります。その一つがプロパティのオーバーラップです。

コンポーネントにミックスインをインポートすると、コンポーネントコードにミックスインコードが結合されます。もし、同じ名前のプロパティがあったらどうなるでしょう?

この場合、コンポーネントプロパティが優先されます。コンポーネントプロパティは、ミックスインプロパティよりも優先度が高いためです。ミックスインに高い優先度を持たせたい時、命名規則に基づいた名前をミックスインにつけることでオーバーラッピングを防ぐことができます。

コンポーネントプロパティとミックスインプロパティを区別するには、$_を使います。$_を使う理由として、

  • VueJSの命名規則として定義されている
  • _はVueのプライベートプロパティを定義するために既に使用されているため、使用できない
  • $はVueのエコシステムで特別なインスタンスのプロパティのため、使用できない

$_createUserのように、$_をつけることで読みやすくなり、コンポーネントとミックスインの区別がしやすくなります。

13. ディレクティブの書き方には一貫性を持たせる

v-onなどのディレクティブには省略記法があります。

  • v-on@
  • v-bind:
  • v-slot#

省略記法を使うことは全く問題ありませんがどちらか一方を使い、プロジェクト内で一貫性を持たせましょう。結果的に読みやすいコードになります。

14. createdwatchでメソッドを呼ばない

初心者によくある間違いは、createdwatch両方でメソッドを呼んでしまうことです。コンポーネントがイニシャライズされた時にwatchフックを動かしたいがためです。

// 悪い例

created: () {
  this.changeName()
},
methods: {
  changeName() {
    console.log('this is my name!');
  }
},
watch () {
  property() {
    this.changeName()
  }
}

一見問題ない様に見えますが、created()watch()でコードが重複しています。

解決方法として、関数をwatch()に移動し、immediatehandlerプロパティを宣言すると、コンポネントがイニシャライズされた時に関数が実行されます。

  1. handler (newVal, oldVal) → watcherメソッドのことです
  2. immediate: true → インスタンスが作られた時にハンドラーが動くようになります
vue.js
// 良い例
watch: {
  myProperty: {
    immediate: true,
    handler() {
      this.changeName();
    }
  }
},
methods: {
  changeName() {
     console.log('this is my name!');
  }
}


// さらに良い例
watch: {
  myProperty: {
    immediate: true,
    handler() {
      console.log('this is my name!'); // 一文なのでmethodsに関数を定義しない
    }
  }
}

15. テンプレート構文には基本的なJavaScript構文しか含めない

テンプレートの中にロジックを書きたくなる気持ちはわかりますが、テンプレートのコードが複雑になり、読みにくくなってしまいます。

<!-- 悪い例 -->

<p>
 {{
   function () {
    return this.fullName.split(" ").map(function (word) {
      return word[0][0].toUpperCase()
    }).join('')
  }
 }}
</p>

基本的に、テンプレート内で何が表示されるかは直感的にわかるようにしたいですよね。そうするために、上記の様な複雑な構文は名前をつけ、適切なコンポーネントオプションに入れましょう。

複雑な構文を分割すると再利用できるのも大きな理由です。

<p>{{ initialName }}<p>
vue.js
computed: {
  initialName: function () {
    return this.fullName.split(" ").map(function (word) {
      return word[0][0].toUpperCase()
    }).join('')
  }
}

JavaScript+Vueスクール参加者募集中です

株式会社X-HACKでは、JavaScript+Vueを学びたい方を募集しています。興味があれば、ぜひお越しください。詳細は、CONNPASSページよりどうぞ!

参考サイト

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした