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

やさしめ Vue.js チュートリアル(3) ~ コンポーネントによる構成

More than 1 year has passed since last update.

はじめに

こちらは やさしめ Vue.js チュートリアル(2) ~ データとイベントの制御 の続きです。

今までは表示内容を全て App.vue の1ファイルに詰め込んでました。
このまま App.vue を拡張していくとカオスになるので、
コンポーネント分割の方法を学びます :muscle:

ハンズオン

1. コンポーネント化(components, props)

では、カード部分をコンポーネントとして切り出してみましょう。

src/components 内に、新たに TaskCard.vue を追加します。12

TaskCard.vue
<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 で使うよう修正します。

不要になったスタイル部分を削除

App.vue
- <style>
- .task-card {
-   border: solid 1px;
-   margin: 5px;
-   padding: 2px;
-   width: 250px;
- }
- </style>
+ <style></style>

スクリプト部分でコンポーネント登録

App.vue
  <script>
+ import TaskCard from "@/components/TaskCard.vue";
+
  export default {
    name: "App",
+   components: {
+     TaskCard
+   },
    computed: {

テンプレート部分でコンポーネントを使うよう修正

App.vue
-  <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" />

:zap: 動作 :zap:
修正前と同じように、タスクの追加ができていれば OK です :ok_hand:

:book: ソースコード :book:
https://github.com/taiju59/lovely-vue/commit/120236e09bfb2af459b422e134e76b47ebfb76aa

:bulb: ポイント :bulb:

  • コンポーネントを .vue ファイルとして切り出すことができる
    1. .vue ファイル作成
    2. 親コンポーネントで import
      • Vue CLI ではパス文字列の @src ディレクトリのエイリアスとなります
    3. components でコンポーネント登録
    4. テンプレート部分で登録したコンポーネントを使う
  • props でプロパティを定義
  • 親コンポーネントから通常のHTML属性と同様に :hoge でプロパティに値を渡せる3

2. 親コンポーネントへのイベント伝搬(emit)

親コンポーネントへのイベント伝搬の方法について学ぶため、
タスクの削除機能を実装してみましょう。

いくつかの段階を踏んで実装していきます。

削除ボタンの設置

まず、タスクの削除ボタンを設置します。

テンプレート部分

TaskCard.vue
<template>
  <li class="task-card">
-     {{ text }}
+     <span>{{ text }}</span>
+     <button @click="remove">削除</button>
  </li>
</template>

スクリプト部分

TaskCard.vue
      text: {
        type: String,
        default: ""
      }
+   },
+   methods: {
+     remove() {
+       // TODO: 削除機能の実装
+       console.log("remove");
+     }
    }
  }
};
</script>

スタイル部分

TaskCard.vue
    word-break: break-all;
  }
+ .task-card > button {
+   float: right;
+ }
</style>

:zap: 動作 :zap:
カードに削除ボタンが表示されたはずです。
削除ボタンを押すとコンソールに「remove」と出力されれば OK です :ok_hand:

:book: ソースコード :book:
https://github.com/taiju59/lovely-vue/commit/1409782f285c7b0427e1651beb07a29b8a3582c4

削除するカードの判別

「remove」と表示されるだけではどのカードか判別できませんね。
App から TaskCardid を渡すよう修正します。4

まずは TaskCard のプロパティに id を定義します。

TaskCard.vue
  props: {
+   id: {
+     type: Number,
+     required: true
+   },
    text: {
...
    remove() {
      // TODO: 削除機能の実装
-     console.log("remove");
+     console.log(`remove: ${this.id}`);

App から TaskCardid を渡します。

App.vue
- <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" を追加しただけです)

:zap: 動作 :zap:
さて、これで削除ボタンを押せば、「remove: 1」のように
削除したいタスクの id まで出力されるようになったはずです :ok_hand:

:book: ソースコード :book:
https://github.com/taiju59/lovely-vue/commit/71b9126a7d7348dae46cc4b6d28494f1a431beb2

emitを使ってタスクの削除

子コンポーネントで発生したイベントをどうやって親コンポーネントに伝えるのか?
ここで使うのが emit です。

TaskCard.vue から emit で親コンポーネントへイベントを伝えます。

TaskCard.vue
  remove() {
-   // TODO: 削除機能の実装
    console.log(`remove ${this.id}`);
+   this.$emit("remove-task", this.id);
  }

App.vue でこれを受け取ります。

App.vue
  <TaskCard
    v-for="todo in todos"
    :id="todo.id"
    :key="todo.id"
    :text="todo.text"
+   @remove-task="remove"
  />

また、実際に todo 削除する関数を methods に追加します。

App.vue
  methods: {
    ...
+   remove(id) {
+     this.todos = this.todos.filter(todo => todo.id !== id);
+   }

(特定 id を持つ todo 以外の配列を再生成することで実質的に削除しています)

:zap: 動作 :zap:
さて、これでようやくタスクが削除されるようになったかと思います :ok_hand:

:book: ソースコード :book:
https://github.com/taiju59/lovely-vue/commit/a6e0ea5c27c95cb3a7f3aacecd6f2943a14d5795

:bulb: ポイント :bulb:

  • emit で親コンポーネントへイベント伝搬
  • emit されたイベントは、親コンポーネントでデフォルトのイベントのように v-on:hoge または @hoge で受け取れる

おまけ(v-if)

参考: https://jp.vuejs.org/v2/guide/conditional.html#v-if

v-if を使ってタスクが1つもないときは「タスクはありません」と表示させます。

App.vue
- <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>

:zap: 動作 :zap:
全てのタスクを削除すると「タスクはありません」と画面上に表示されれば OK です :ok_hand:

:book: ソースコード :book:
https://github.com/taiju59/lovely-vue/commit/b65fec24cbd707771c947a69b69138d205e1b5a6?diff=unified

:bulb: ポイント :bulb:

  • v-if で条件を満たす場合のみの描画が可能
    • v-else-idv-else-if もあります

おわりに

今回はコンポーネントによる構成を学びました。

これでアプリケーションが大きくなってもファイルを分割することで
なんとかまあカオスにならずにやっていけるんじゃないかなと思います。

ここまでで Vue.js の記法としては一旦終わりですが、
まだまだ伝えられていないことは多いです。

作る前にもう少し何かを読みたい人はとにかく公式ドキュメントを読むのがオススメです。
このチュートリアルをやった人であれば、サクサクと読めるんじゃないかと信じています :thumbsup:
https://jp.vuejs.org/v2/guide

次回は Vue.js そのものから少し離れて、知らないままでも作れるけど
当たり前に知っておきたいよね、みたいな周辺知識を紹介できればと思います

次回 :point_right: やさしめ Vue.js チュートリアル(4) ~ 周辺ツールや情報紹介

Lovely Vue :heartbeat:


  1. コンポーネント名はスタイルガイドで複数単語の命名を必須レベルに推奨されている 

  2. スタイルに 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

  3. :v-bind: の省略記法 

  4. todo ごと渡した方が綺麗かもしれません。今回はわかりやすさ重視で分けて渡します 

taiju59
Flutterスキスキ
https://note.com/taiju____
ferix
R&Dを中心に主に受託開発でソフトウェアを開発するエンジニア集団
https://www.ferix.jp
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
ユーザーは見つかりませんでした