はじめに
こちらは やさしめ Vue.js チュートリアル(2) ~ データとイベントの制御 の続きです。
今までは表示内容を全て App.vue の1ファイルに詰め込んでました。
このまま App.vue を拡張していくとカオスになるので、
コンポーネント分割の方法を学びます 
ハンズオン
1. コンポーネント化(components, props)
では、カード部分をコンポーネントとして切り出してみましょう。
src/components 内に、新たに TaskCard.vue を追加します。12
<template>
  <li class="task-card">
    {{ text }}
  </li>
</template>
<script>
export default {
  name: "TaskCard",
  props: {
    text: {
      type: String,
      default: ""
    }
  }
};
</script>
<style>
.task-card {
  border: solid 1px;
  margin: 5px;
  padding: 2px;
  width: 250px;
}
</style>
このコンポーネントを App.vue で使うよう修正します。
不要になったスタイル部分を削除
- <style>
- .task-card {
-   border: solid 1px;
-   margin: 5px;
-   padding: 2px;
-   width: 250px;
- }
- </style>
+ <style></style>
スクリプト部分でコンポーネント登録
  <script>
+ import TaskCard from "@/components/TaskCard.vue";
+
  export default {
    name: "App",
+   components: {
+     TaskCard
+   },
    computed: {
テンプレート部分でコンポーネントを使うよう修正
-  <li v-for="todo in todos" :key="todo.id" class="task-card">
-    {{ todo.text }}
-  </li>
+  <TaskCard v-for="todo in todos" :key="todo.id" :text="todo.text" />
 動作
 動作 
修正前と同じように、タスクの追加ができていれば OK です 
 ソースコード
 ソースコード 
https://github.com/taiju59/lovely-vue/commit/120236e09bfb2af459b422e134e76b47ebfb76aa
 ポイント
 ポイント 
- コンポーネントを .vueファイルとして切り出すことができる- 
.vueファイル作成
- 親コンポーネントで import- Vue CLI ではパス文字列の @はsrcディレクトリのエイリアスとなります
 
- Vue CLI ではパス文字列の 
- 
componentsでコンポーネント登録
- テンプレート部分で登録したコンポーネントを使う
 
- 
- 
propsでプロパティを定義
- 親コンポーネントから通常のHTML属性と同様に :hogeでプロパティに値を渡せる3
2. 親コンポーネントへのイベント伝搬(emit)
親コンポーネントへのイベント伝搬の方法について学ぶため、
タスクの削除機能を実装してみましょう。
いくつかの段階を踏んで実装していきます。
削除ボタンの設置
まず、タスクの削除ボタンを設置します。
テンプレート部分
<template>
  <li class="task-card">
-     {{ text }}
+     <span>{{ text }}</span>
+     <button @click="remove">削除</button>
  </li>
</template>
スクリプト部分
      text: {
        type: String,
        default: ""
      }
+   },
+   methods: {
+     remove() {
+       // TODO: 削除機能の実装
+       console.log("remove");
+     }
    }
  }
};
</script>
スタイル部分
    word-break: break-all;
  }
+ .task-card > button {
+   float: right;
+ }
</style>
 動作
 動作 
カードに削除ボタンが表示されたはずです。
削除ボタンを押すとコンソールに「remove」と出力されれば OK です 
 ソースコード
 ソースコード 
https://github.com/taiju59/lovely-vue/commit/1409782f285c7b0427e1651beb07a29b8a3582c4
削除するカードの判別
「remove」と表示されるだけではどのカードか判別できませんね。
App から TaskCard へ id を渡すよう修正します。4
まずは TaskCard のプロパティに id を定義します。
  props: {
+   id: {
+     type: Number,
+     required: true
+   },
    text: {
...
    remove() {
      // TODO: 削除機能の実装
-     console.log("remove");
+     console.log(`remove: ${this.id}`);
App から TaskCard へ id を渡します。
- <TaskCard v-for="todo in todos" :key="todo.id" :text="todo.text" />
+ <TaskCard
+   v-for="todo in todos"
+   :id="todo.id"
+   :key="todo.id"
+   :text="todo.text"
+ />
(自動整形の都合で改行されただけで :id="todo.id" を追加しただけです)
 動作
 動作 
さて、これで削除ボタンを押せば、「remove: 1」のように
削除したいタスクの id まで出力されるようになったはずです 
 ソースコード
 ソースコード 
https://github.com/taiju59/lovely-vue/commit/71b9126a7d7348dae46cc4b6d28494f1a431beb2
emitを使ってタスクの削除
子コンポーネントで発生したイベントをどうやって親コンポーネントに伝えるのか?
ここで使うのが emit です。
TaskCard.vue から emit で親コンポーネントへイベントを伝えます。
  remove() {
-   // TODO: 削除機能の実装
    console.log(`remove ${this.id}`);
+   this.$emit("remove-task", this.id);
  }
App.vue でこれを受け取ります。
  <TaskCard
    v-for="todo in todos"
    :id="todo.id"
    :key="todo.id"
    :text="todo.text"
+   @remove-task="remove"
  />
また、実際に todo 削除する関数を methods に追加します。
  methods: {
    ...
+   remove(id) {
+     this.todos = this.todos.filter(todo => todo.id !== id);
+   }
(特定 id を持つ todo 以外の配列を再生成することで実質的に削除しています)
 動作
 動作 
さて、これでようやくタスクが削除されるようになったかと思います 
 ソースコード
 ソースコード 
https://github.com/taiju59/lovely-vue/commit/a6e0ea5c27c95cb3a7f3aacecd6f2943a14d5795
 ポイント
 ポイント 
- 
emitで親コンポーネントへイベント伝搬- 第一引数にイベント名、第二引数以降に引数を渡します(https://jp.vuejs.org/v2/api/#vm-emit)
 
- 
emitされたイベントは、親コンポーネントでデフォルトのイベントのようにv-on:hogeまたは@hogeで受け取れる
おまけ(v-if)
参考: https://jp.vuejs.org/v2/guide/conditional.html#v-if
v-if を使ってタスクが1つもないときは「タスクはありません」と表示させます。
- <ul>
-   <TaskCard
-     v-for="todo in todos"
-     :id="todo.id"
-     :key="todo.id"
-     :text="todo.text"
-     @remove-task="remove"
-   />
- </ul>
+ <template v-if="todos.length === 0">
+   タスクはありません
+ </template>
+ <template v-else>
+ <ul>
+   <TaskCard
+     v-for="todo in todos"
+     :id="todo.id"
+     :key="todo.id"
+     :text="todo.text"
+     @remove-task="remove"
+     />
+   </ul>
+ </template>
 動作
 動作 
全てのタスクを削除すると「タスクはありません」と画面上に表示されれば OK です 
 ソースコード
 ソースコード 
https://github.com/taiju59/lovely-vue/commit/b65fec24cbd707771c947a69b69138d205e1b5a6?diff=unified
 ポイント
 ポイント 
- 
v-ifで条件を満たす場合のみの描画が可能- 
v-else-id、v-else-ifもあります
 
- 
おわりに
今回はコンポーネントによる構成を学びました。
これでアプリケーションが大きくなってもファイルを分割することで
なんとかまあカオスにならずにやっていけるんじゃないかなと思います。
ここまでで Vue.js の記法としては一旦終わりですが、
まだまだ伝えられていないことは多いです。
作る前にもう少し何かを読みたい人はとにかく公式ドキュメントを読むのがオススメです。
このチュートリアルをやった人であれば、サクサクと読めるんじゃないかと信じています 
https://jp.vuejs.org/v2/guide
次回は Vue.js そのものから少し離れて、知らないままでも作れるけど
当たり前に知っておきたいよね、みたいな周辺知識を紹介できればと思います
次回  やさしめ Vue.js チュートリアル(4) ~ 周辺ツールや情報紹介
 やさしめ Vue.js チュートリアル(4) ~ 周辺ツールや情報紹介
Lovely Vue 
- 
コンポーネント名はスタイルガイドで複数単語の命名を必須レベルに推奨されている ↩ 
- 
スタイルに scoped属性を付与することで CSS のスコープをこのコンポーネント内に限定することができます(https://jp.vuejs.org/v2/guide/comparison.html#%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%B3%E3%83%BC%E3%83%97-CSS%EF%BC%88Scoped-CSS%EF%BC%89) ↩
- 
todoごと渡した方が綺麗かもしれません。今回はわかりやすさ重視で分けて渡します ↩
