はじめに
こちらは やさしめ 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) ~ 周辺ツールや情報紹介
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
ごと渡した方が綺麗かもしれません。今回はわかりやすさ重視で分けて渡します ↩