Vueの基礎が詰まったTodoリスト

June 25, 2021


CDN での実装

<!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>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
  </head>
  <body>
    <style type="text/css">
      table {
        width: 300px;
        border-collapse: collapse;
        border-spacing: 0;
      }

      table th,
      table td {
        padding: 10px 0;
        text-align: center;
      }

      .finished {
        text-decoration: line-through;
      }

      button:disabled {
        background-color: #ccc;
      }
    </style>
    <div id="app">
      <input type="text" v-model="name" placeholder="hogehoge" />
      <button @click="addTodo" :disabled="!name.length">ADD</button>
      <p>残タスク数:{{ unfinishedTodoCount }}</p>
      <p>終了率:{{ finishedRate }}%</p>
      <table>
        <thead>
          <tr>
            <th>
              タイトル
            </th>
            <th>
              完了済み
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(todo, i) in todos" :key="i">
            <td :class="todo.done ? 'finished' : ''">
              {{ todo.name }}
            </td>
            <td>
              <input type="checkbox" v-model="todo.done" />
            </td>
            <td>
              <button @click="deleteTodo(i)">
                削除
              </button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          name: "",
          todos: [{ name: "hoge", done: false }]
        },
        beforeCreate() {
          console.log("beforeCreate!");
        },
        created() {
          console.log("created!");
        },
        beforeMount() {
          console.log("beforeMount!");
        },
        mounted() {
          console.log("mounted!");
        },
        methods: {
          // やることを追加するメソッド
          addTodo() {
            this.todos.push({ name: this.name, done: false });
            this.name = "";
          },
          // やることを削除するメソッド
          deleteTodo(i) {
            this.todos.splice(i, 1);
          }
        },
        computed: {
          // 終了していないタスクの数を返す
          unfinishedTodoCount() {
            return this.todos.filter(todo => !todo.done).length;
          },
          // 終了しているタスクの割合を返す
          finishedRate() {
            if (!this.todos.length) return 0;

            const finishedTodoCount = this.todos.filter(todo => todo.done)
              .length;
            return (finishedTodoCount / this.todos.length) * 100;
          }
        },
        watch: {
          // nameを監視
          name(newVal, oldVal) {
            console.log(`newVal: ${newVal}`);
            console.log(`oldVal: ${oldVal}`);
          }
        },
        beforeUpdate() {
          console.log("beforeUpdate!");
        },
        updated() {
          console.log("updated!");
        },
        beforeDestroy() {
          console.log("beforeDestroy!");
        },
        destroyed() {
          console.log("destroyed!");
        }
      });
    </script>
  </body>
</html>

単一ファイルコンポーネントでの実装

App.vue
<template>
  <div id="app">
    <input type="text" v-model="name" placeholder="hogehoge" />
    <button @click="addTodo" :disabled="!name.length">ADD</button>
    <p>残タスク数:{{ unfinishedTodoCount }}</p>
    <p>終了率:{{ finishedRate }}%</p>
    <table>
      <thead>
        <tr>
          <th>
            タイトル
          </th>
          <th>
            完了済み
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(todo, i) in todos" :key="i">
          <td :class="todo.done ? 'finished' : ''">
            {{ todo.name }}
          </td>
          <td>
            <input type="checkbox" v-model="todo.done" />
          </td>
          <td>
            <button @click="deleteTodo(i)">
              削除
            </button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  data: () => ({
    name: "",
    todos: [{ name: "hoge", done: false }]
  }),
  beforeCreate() {
    console.log("beforeCreate!");
  },
  created() {
    console.log("created!");
  },
  beforeMount() {
    console.log("beforeMount!");
  },
  mounted() {
    console.log("mounted!");
  },
  methods: {
    // やることを追加するメソッド
    addTodo() {
      this.todos.push({ name: this.name, done: false });
      this.name = "";
    },
    // やることを削除するメソッド
    deleteTodo(i) {
      this.todos.splice(i, 1);
    }
  },
  computed: {
    // 終了していないタスクの数を返す
    unfinishedTodoCount() {
      return this.todos.filter(todo => !todo.done).length;
    },
    // 終了しているタスクの割合を返す
    finishedRate() {
      if (!this.todos.length) return 0;

      const finishedTodoCount = this.todos.filter(todo => todo.done).length;
      return (finishedTodoCount / this.todos.length) * 100;
    }
  },
  watch: {
    // nameを監視
    name(newVal, oldVal) {
      console.log(`newVal: ${newVal}`);
      console.log(`oldVal: ${oldVal}`);
    }
  },
  beforeUpdate() {
    console.log("beforeUpdate!");
  },
  updated() {
    console.log("updated!");
  },
  beforeDestroy() {
    console.log("beforeDestroy!");
  },
  destroyed() {
    console.log("destroyed!");
  }
};
</script>

<style>
table {
  width: 300px;
  border-collapse: collapse;
  border-spacing: 0;
}

table th,
table td {
  padding: 10px 0;
  text-align: center;
}

.finished {
  text-decoration: line-through;
}

button:disabled {
  background-color: #ccc;
}
</style>

LocalStorage を活用したデータの永続化

<!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>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
  </head>
  <body>
    <style type="text/css">
      table {
        width: 300px;
        border-collapse: collapse;
        border-spacing: 0;
      }

      table th,
      table td {
        padding: 10px 0;
        text-align: center;
      }

      .finished {
        text-decoration: line-through;
      }

      button:disabled {
        background-color: #ccc;
      }
    </style>
    <div id="app">
      <input type="text" v-model="name" placeholder="hogehoge" />
      <button @click="addTodo" :disabled="!name.length">ADD</button>
      <p>残タスク数:{{ unfinishedTodoCount }}</p>
      <p>終了率:{{ finishedRate }}%</p>
      <table>
        <thead>
          <tr>
            <th>タイトル</th>
            <th>完了済み</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(todo, i) in todos" :key="i">
            <td :class="todo.done ? 'finished' : ''">{{ todo.name }}</td>
            <td>
              <input type="checkbox" v-model="todo.done" />
            </td>
            <td>
              <button @click="deleteTodo(i)">削除</button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <script>
      new Vue({
        el: "#app",
        data: {
          name: "",
          todos: JSON.parse(window.localStorage.getItem("todos")) || []
        },
        beforeCreate() {
          console.log("beforeCreate!");
        },
        created() {
          console.log("created!");
        },
        beforeMount() {
          console.log("beforeMount!");
        },
        mounted() {
          console.log("mounted!");
        },
        methods: {
          // やることを追加するメソッド
          addTodo() {
            this.todos.push({ name: this.name, done: false });
            window.localStorage.setItem("todos", JSON.stringify(this.todos));
            this.name = "";
          },
          // やることを削除するメソッド
          deleteTodo(i) {
            this.todos.splice(i, 1);
            window.localStorage.setItem("todos", JSON.stringify(this.todos));
          }
        },
        computed: {
          // 終了していないタスクの数を返す
          unfinishedTodoCount() {
            return this.todos.filter(todo => !todo.done).length;
          },
          // 終了しているタスクの割合を返す
          finishedRate() {
            if (!this.todos.length) return 0;

            const finishedTodoCount = this.todos.filter(
              todo => todo.done
            ).length;
            return (finishedTodoCount / this.todos.length) * 100;
          }
        },
        watch: {
          // nameを監視
          name(newVal, oldVal) {
            console.log(`newVal: ${newVal}`);
            console.log(`oldVal: ${oldVal}`);
          }
        },
        beforeUpdate() {
          console.log("beforeUpdate!");
        },
        updated() {
          console.log("updated!");
        },
        beforeDestroy() {
          console.log("beforeDestroy!");
        },
        destroyed() {
          console.log("destroyed!");
        }
      });
    </script>
  </body>
</html>