あまブログ

ドキドキ......ドキドキ2択クイ〜〜〜〜〜〜〜ズ!!

【Vue3】ToDoリストアプリを作る

この記事ではVue.jsでToDoリストアプリを作成する方法を紹介します。

HTML, CSS, JSだけを使用し、データはLocalStorageに保存し、Vue3でOptions APIを使用します。

1. 実行環境

  • macOS:13.0.1
  • Node.js:18.12.1
  • npm:8.19.2
  • Vue:3.2.45

2. ToDoアプリの要件

以下のToDoアプリを作成します。

  • ToDoが登録できる
  • ToDoが一覧表示できる
  • ToDoが編集できる
  • ToDoが削除できる
  • ToDoの処理状態を変更できる
  • ToDoの処理状態ごとに表示できる

3. 作成手順

3-1. ToDoの登録と一覧表示

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ToDoアプリ</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div id="app">
      <form @submit.prevent="addTodo">
        <input v-model="newTodo" />
        <button>登録</button>
      </form>
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          {{ todo.text }}
        </li>
      </ul>
    </div>
    <script type="module" src="app.js"></script>
  </body>
</html>

app.js

import { createApp } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";

let id = 0;

createApp({
  data() {
    return {
      newTodo: "",
      todos: [],
    };
  },
  methods: {
    addTodo() {
      this.todos.push({ id: id++, text: this.newTodo });
      this.newTodo = "";
    },
  },
}).mount("#app");

3-2. ToDoの削除

index.html

  <div id="app">
    省略
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
+      <button @click="removeTodo(todo)">削除</button>
      </li>
    </ul>
  </div>

app.js

  methods: {
    // 省略
+    removeTodo(todo) {
+      this.todos = this.todos.filter((t) => t !== todo);
+    },
  },

3-3. ToDoの編集

index.html

 <ul>
   <li v-for="todo in todos" :key="todo.id">
+    <input
+      v-if="todo === editingTodo"
+      v-model="todo.text"
+      @blur="doneEdit(todo)"
+      @keyup.enter="doneEdit(todo)"
+    />
+    <label v-else>{{ todo.text }}</label>
+    <button @click="doneEdit(todo)" v-if="todo === editingTodo">
+      確定
+    </button>
+    <button @click="editTodo(todo)" v-else>編集</button>
     <button @click="removeTodo(todo)">削除</button>
   </li>
 </ul>

app.js

  data() {
    return {
      // 省略
+     editingTodo: null,
    };
  },
  methods: {
    // 省略
+   editTodo(todo) {
+     this.editingTodo = todo;
+   },
+   doneEdit(todo) {
+     this.editingTodo = null;
+     todo.text = todo.text.trim();
+     if (!todo.text) {
+       this.removeTodo(todo);
+     }
+   },
  },

3-4. データをLocalStorageに保存

app.js

// 省略
+ const STORAGE_KEY = "vue-todoapp";

  createApp({
    data() {
      return {
        newTodo: "",
+       todos: JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"),
        editingTodo: null,
      };
    },
    methods: {
      addTodo() {
        this.todos.push({ id: id++, text: this.newTodo, done: false });
+       localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos));
        this.newTodo = "";
      },
      removeTodo(todo) {
        this.todos = this.todos.filter((t) => t !== todo);
+       localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos));
      },
      editTodo(todo) {
        this.editingTodo = todo;
      },
      doneEdit(todo) {
        this.editingTodo = null;
        todo.text = todo.text.trim();
+       localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos));
        if (!todo.text) {
          this.removeTodo(todo);
        }
      },
    },
  }).mount("#app");

3-5. ToDoの処理状態の変更

index.html

  <li v-for="todo in todos" :key="todo.id">
+  <input type="checkbox" v-model="todo.done" />
    <input
      v-if="todo === editingTodo"
      v-model="todo.text"
      @blur="doneEdit(todo)"
      @keyup.enter="doneEdit(todo)"
    />
+  <label v-else :class="{ done: todo.done }">{{ todo.text }}</label>
    <button @click="doneEdit(todo)" v-if="todo === editingTodo">
      確定
    </button>
    <button @click="editTodo(todo)" v-else>編集</button>
    <button @click="removeTodo(todo)">削除</button>
  </li>
  • チェックボックスにチェックをつけるとtodo.doneの値がtrueに、チェックを外すとfalseになる
  • todo.doneの値によってdoneクラスが動的に切り替わる

app.js

  addTodo() {
+   this.todos.push({ id: id++, text: this.newTodo, done: false });
    localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos));
    this.newTodo = "";
  },

style.css

+ .done {
+   text-decoration: line-through;
+ }

3-6. ToDoの処理状態ごとの表示

index.html

  <ul>
+   <li v-for="todo in filteredTodos" :key="todo.id">
      <!-- 省略 -->
    </li>
  </ul>
+ <ul class="filters">
+   <li>
+     <button
+       @click="changeVisibility('all')"
+       :class="{ selected: visibility === 'all' }"
+     >
+       すべて
+     </button>
+   </li>
+   <li>
+     <button
+       @click="changeVisibility('active')"
+       :class="{ selected: visibility === 'active' }"
+     >
+       作業中
+     </button>
+   </li>
+   <li>
+     <button
+       @click="changeVisibility('done')"
+       :class="{ selected: visibility === 'done' }"
+     >
+       完了
+     </button>
+   </li>
+ </ul>
  • 各ボタンをクリックすることでvisibilityの値をall,active,doneに切り替える
  • visibilityの値によってselectedクラスが該当のボタンに付与される

app.js

// 省略
+ const filters = {
+   all: (todos) => todos,
+   active: (todos) => todos.filter((todo) => !todo.done),
+   done: (todos) => todos.filter((todo) => todo.done),
+ };

  createApp({
    data() {
      return {
        // 省略
+       visibility: "all",
      };
    },
+   computed: {
+     filteredTodos() {
+       return filters[this.visibility](this.todos);
+     },
+   },
    methods: {
      // 省略
+     changeVisibility(visibility) {
+       this.visibility = visibility;
+     },
    },
  }).mount("#app");
  • return filters[this.visibility](this.todos);
    • this.visibilityの値によって(all,active,done)、呼び出すメソッドが切り替わる

style.css

/* 省略 */
+ ul {
+   padding: 0;
+ }
+ .filters li {
+     display: inline;
+ }
+ .filters li button.selected {
+     border-color: #CE4646;
+ }

【参考】