なるはや Vue SPA入門：コンポーネント

Vue CLI3 で作成した SPA（Single Page Application）プロジェクト上で、段階的に Vue.js を学んで行きましょう。

[目次はこちら](https://qiita.com/tsubo/items/24df49ef2f1ba87984d1)

今回はコンポーネント編です。
TodoList を作りながらコンポーネントの使い方を学びます。

## 前提事項

[Vue の基本](https://qiita.com/tsubo/items/02a18b1899128505682e) が完了していること。 

## ページの追加

新規に Todo List ページを作成します。

- TodoList.vue を作成
- router.js を修正
- App.vue にナビゲーションを追加

やり方を忘れてしまった人は「[Vue Router 編](https://qiita.com/tsubo/items/fe91e95e52ee82e05681)」を振り返ってください。

```vue:src/views/TodoList.vue
<template>
  <div class="todolist">
    <h1>Todo</h1>
    <ul v-if="todos.length">
      <li
        v-for="todo in todos"
        :key="todo.id"
      >{{ todo.text }}</li>
    </ul>
    <p v-else>
      TODO 一覧はありません
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      todos: [
        { id: 1, text: "Learn Vue" },
        { id: 2, text: "Learn about single file components" },
        { id: 3, text: "Learn about components" }
      ]
    };
  }
};
</script>

<style>
h1 {
  text-align: center;
}

.todolist {
  max-width: 400px;
  margin: 0 auto;
  text-align: left;
}
</style>
```

## コンポーネントの作成とプロパティによる値渡し

Todo List から Todo List Item を別コンポーネントに切り出します。

TodoListItem.vue コンポーネントを作成します。

```vue:src/components/TodoListItem.vue
<template>
  <!-- todo プロパティのテキストを表示 -->
  <li>{{ todo.text }}</li>
</template>

<script>
export default {
  // 親コンポーネントから todo プロパティを受け取る
  props: ["todo"]
};
</script>
```
親コンポーネントから子コンポーネントに値を渡すにはプロパティを使用します。
上記では `todo` プロパティを受け取れるよう宣言しています。


TodoList.vue を下記の様に修正します。

```vue:TodoList.vue
<template>
  <div class="todolist">
    <h1>Todo</h1>
    <ul v-if="todos.length">
      <!-- TodoListItem コンポーネントを使います -->
      <!-- v-bind を使って動的に todo プロパティを渡します -->
      <TodoListItem
        v-for="todo in todos"
        :key="todo.id"
        :todo="todo"
      />
    </ul>
    <p v-else>
      TODO 一覧はありません
    </p>
  </div>
</template>

<script>
// TodoListItem コンポーネントを import します
import TodoListItem from "../components/TodoListItem";

export default {
  // 使用するコンポーネントを Vue に伝えます
  components: {
    TodoListItem
  },
  data() {
    // ...
  }
};
</script>

<style>
/* ... */
</style>
```

JavaScript のところで先程作成した`TodoListItem.vue`をインポートして、Vue オブジェクトの`components`属性に設定しています。こうすることで、HTML のところで、`TodoListItem`タグが使用できるようになります。

HTML のところでは`li`タグの代わりに`TodoListItem`タグを使用するように変更しました。子コンポーネントに値を渡すために`todo`プロパティに`todo`オブジェクトをバインドしています。

## プロパティのバリデーション

プロパティは以下のように記述することでバリデーションチェックをすることができます。

```js
export default {
  props: {
    // 基本的な型の検査 (`null` と `undefined` は全てのバリデーションにパスします)
    propA: Number,
    // 複数の型の許容
    propB: [String, Number],
    // 文字列型を必須で要求する
    propC: {
      type: String,
      required: true
    },
    // デフォルト値つきの数値型
    propD: {
      type: Number,
      default: 100
    },
    // デフォルト値つきのオブジェクト型
    propE: {
      type: Object,
      // オブジェクトもしくは配列のデフォルト値は
      // 必ずそれを生み出すための関数を返す必要があります。
      default: function () {
        return { message: 'hello' }
      }
    },
    // カスタマイズしたバリデーション関数
    propF: {
      validator: function (value) {
        // プロパティの値は、必ずいずれかの文字列でなければならない
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
}
```

`TodoListItem.vue` もバリデーションチェックをするよう修正します。

```js:TodoListItem.vue
props: {
  todo: {
    type: Object,   // オブジェクト型であること
    required: true  // 必須項目
  }
}
```

## イベント

親コンポーネントから子コンポーネントへはプロパティを使って値を渡すことができますが、子コンポーネントから親コンポーネントへ値を渡すことは出来ません。その代わりにイベントを使って通信することができます。

### イベントの発火

`TodoListItem.vue` に削除ボタンを設け、`remove`イベントを発火するよう修正します。

```vue:TodoListItem.vue
<li>
  {{ todo.text }}
  <button @click="$emit('remove', todo.id)">x</button>
</li>
```
`botton` がクリックされると `$emit('イベント名', ペイロード)` でイベントを発火します。

### イベントハンドリング

`TodoList.vue` で `remove` イベントをハンドリングして Todo を削除します。

```html:TodoList.vue
<!-- remove イベントをハンドリングして removeTodo メソッドをコールします -->
<TodoListItem
  v-for="todo in todos"
  :key="todo.id"
  :todo="todo"
  @remove="removeTodo"
/>
```
```js:TodoList.vue
methods: {
  removeTodo(idToRemove) {
    this.todos = this.todos.filter(todo => {
      return todo.id !== idToRemove;
    });
  }
}
```

`removeTod`メソッドには`remove`イベントが発火された時のペイロードが引数として渡されます（この場合は todo の id です）。

## 演習

`TodoInputText.vue` コンポーネントを作成して、`TodoList.vue` でそれを使って TODO を追加できるようにしてください。

## 次回

[Vue.js devtools](https://qiita.com/tsubo/items/d03ef6aa7ca187527860)