クラウドインテグレーションサービス「雲斗」のブログ

芝公園にある創研情報株式会社がAWS を 中心にクラウドの基本から便利な使いかたまでをお伝えしていきます。

Vue.js

Vue CLI 3を使ってTODOアプリを作ってみる(TODOアプリの作成編その①)

目次

  • 開発環境構築
    • Vue CLI 3 インストール
    • プロジェクト作成
    • 開発サーバ起動
  • Vue.jsの便利なところ
  • TODOアプリの作成
    • 概要
    • 一覧機能
    • サマリ機能 この記事はここまで
    • 追加機能
    • 編集機能
    • 削除機能

概要

TODOアプリは既にできているので、コードの抜粋形式で記載していきます。

数年前にAngular.jsを触ったときは、コンポーネント間の値受け渡しに大ハマりしました。
その辺りVue.jsではどうなのかという確認も込めて、コンポーネントを分けて実装していこうと思います。

今回はVue.jsの学習なのでスタイルは何でもいいのですが、
ブラウザデフォルトだとテンションがあがらないので、
Spectre.cssを使用します。

完成したアプリ

完成したアプリの画面キャプチャはこんな感じです。
完成イメージ

コンポーネントの親子関係は以下のようになっています。

Home
└ TodoList
   ├ TodoAdd
   ├ TodoSummary
   ├ TodoEdit
   └ TodoDel

一覧機能

何の変哲もない、Ajaxでデータを取得して一覧表示する機能です。

created() {
  this.fetchTodos();
},
methods: {
  fetchTodos() {
    axios.get("http://localhost:3000/api/todos").then(res => {
      this.todos = res.data;
    });
  }
}

axiosでデータを取得して、コンポーネント内に保持する関数fetchTodos()createdで呼び出しています。
window.onload = function(){ ... };みたいなものですね“〆(゚_゚*)フムフム

ほかにもbeforeCreatemountedなどのライフサイクルフックが使えるようです!

参考
ライフサイクルダイアグラム

<div
  v-bind:key="todo.id"
  v-for="todo in todos"
>...</div>

リストレンダリングはv-forを使用します。
v-for="変数 in ループ対象"のように使用します。
このように実装することで、v-forを書いた要素(配下含む)を
ループでレンダリングすることができます。

TodoListの全コード

<template>
  <div>
    <TodoAdd v-on:update="fetchTodos"/>
    <TodoSummary v-bind:todos="todos"/>
    <div class="container">
      <div
        class="columns pb-2"
        v-bind:key="todo.id"
        v-for="todo in todos"
      >
        <div class="column col-10">
          <TodoEdit
            v-bind:todo="todo"
            v-on:update="fetchTodos"
          />
        </div>
        <div class="column col-2">
          <TodoDel
            v-bind:todo="todo"
            v-on:update="fetchTodos"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
import TodoAdd from "@/components/TodoAdd.vue";
import TodoSummary from "@/components/TodoSummary.vue";
import TodoEdit from "@/components/TodoEdit.vue";
import TodoDel from "@/components/TodoDel.vue";

export default {
  name: "TodoList",
  components: {
    TodoAdd,
    TodoSummary,
    TodoEdit,
    TodoDel
  },
  data: () => ({
    todos: []
  }),
  created() {
    this.fetchTodos();
  },
  methods: {
    fetchTodos() {
      axios.get("http://localhost:3000/api/todos").then(res => {
        this.todos = res.data;
      });
    }
  }
};
</script>

<style scoped></style>

サマリ機能

タスクの状態が、Todo、Doing、Doneそれぞれ何件あるのかを表示する機能です。

Vue.jsには算出プロパティというものがあり、
テンプレート内にロジックを埋め込むことなく、表示用の整形や算出などができます。
computed内で宣言した関数は、propsdataなどと同じようにテンプレート内で使用できます。

例えば、〇〇コードのようなものを左8桁ゼロ埋めして表示するように作るとして、
左8桁ゼロ埋めする処理を表示部分すべてで呼び出す必要がありますが、
算出プロパティを使うと、そのようなロジックを1か所にまとめることができます。

TodoListからTodoSummaryへの値の受け渡しは以下のようにして実装します。

<TodoSummary v-bind:todos="todos"/>

TodoListでTodoSummaryにtodosを渡す。
TodoSummaryのprops.todosに渡された値が代入される。

props: {
  todos: {
    type: Array,
    default: []
  }
},
computed: {
  todoCount()  { return this.todos.filter(t => t.status.code === 0).length; },
  doingCount() { return this.todos.filter(t => t.status.code === 1).length; },
  doneCount()  { return this.todos.filter(t => t.status.code === 9).length; }
}

受け取ったtodosをステータスごとにカウントする。
ここは全部を同じようにカウントするのではなく、
1個だけカウントして、各データに代入する方が良かったですね(・Θ・;)アセアセ…

注意点
JavaScriptのオブジェクトと配列は参照渡しなので、TodoSummaryコンポーネント内で、props.todosを変更してしまうと、他の部分にも影響があります!!

今回はcomputedを使ってみようということで、上記の注意点は気にせず実装しています。

TodoSummaryの全コード

<template>
  <div class="summary-container">
    <span
      class="badge"
      v-bind:data-badge="todoCount"
    >Todo</span>
    <span
      class="badge"
      v-bind:data-badge="doingCount"
    >Doing</span>
    <span
      class="badge"
      v-bind:data-badge="doneCount"
    >Done</span>
  </div>
</template>

<script>
export default {
  name: "TodoSummary",
  props: {
    todos: {
      type: Array,
      default: []
    }
  },
  computed: {
    todoCount() {
      return this.todos.filter(t => t.status.code === 0).length;
    },
    doingCount() {
      return this.todos.filter(t => t.status.code === 1).length;
    },
    doneCount() {
      return this.todos.filter(t => t.status.code === 9).length;
    }
  }
};
</script>

<style scoped>
.summary-container {
  padding: 1rem 0;
}
</style>

次回

次回は引き続き、各機能の実装を紹介していきます!

-Vue.js

Bitnami