Vue.js超入門【Vue.jsの基本的な使い方③〜TODOアプリ編〜】

こんにちはMinatoです。

前回までは、Vue.jsの基本的な使い方についてご紹介しました。
今回は、前回までの復習として、より実践的なTODOアプリの実装に取り組みたいと思います。

前回の記事をまだ見ていないという方は、以下のリンクからご確認ください。

Vue超入門_1

Vue.js超入門【Vue.jsの基本的な使い方】

2021年5月27日

Vue.js超入門【Vue.jsの基本的な使い方②】

2021年5月28日

アプリの概要

完成形のイメージは以下のようになります。

  • テキストフィールドに文字を入力
  • 「Add」ボタンクリックでTODO追加
  • TODOのチェックボックスで完了、未完了の切り替え
  • 「Delete」ボタンクリックでTODOの削除

ディレクトリとファイルの作成

まず初めに以下の構造で、ディレクトリとファイルを作成してください。

todo/
 ├ index.html
 ├ js/
  ├ main.js
 ├ css/
  ├ main.css

Vueインスタンスとルートテンプレートの作成

ベースのHTMLを定義します。

ここでは、<div id="app">をルートコンポーネントとしてます。
つまり、<div id="app">配下でVue.jsが有効となります。

CDNからVue.jsを読み込みます。同時にmain.cssとmain.jsの読み込みます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/main.css">
</head>
<body>
    <div id="app">
        <h2>TODO APP</h2>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

main.jsで空のVueインスタンスを定義します。

new Vue({
  el: '#app',
})

TODOの追加処理

ベースとなるファイルの準備ができましたので、ここからTODOの追加機能の実装を行います。

まずdataプロパティにnewTaskとtodosを定義します。

  • newTaskはテキストフィールドに入力中の値が格納されます。
  • todosはtodoを格納する配列です。todosには、「Add」をクリックした際に、newTaskの値が新規に追加されます。

続いて、methodsプロパティにメソッドを定義していきます。
addTaskメソッドの処理は以下のようになります。

  1. if(this.newTask === '') returnは入力値が空の場合は、登録処理をスキップするための制御です。
  2. todoオブジェクトを定義し、itemに現在のnewTaskの値をセットします。
  3. this.todos.push(todo)でtodos配列にtodoを追加します。
  4. 最後にnewTaskをリセットしています。
new Vue({
  el: '#app',
  data: {
    newTask: '',
    todos: []
  },
  methods: {
    addTask: function (event) {
      if(this.newTask === '') return

      let todo = {
        item: this.newTask
      }

      this.todos.push(todo)
      this.newTask = ''
    }
  }
})

続いてindex.htmlを修正します。

<form>タグのv-on:submit.preventの記述は、「Add」をクリックした際に画面が再読み込みされるのを防ぐおまじないみたいなものだと思ってください。

v-modelを利用して、dataプロパティのnewTaskと<input>のvalueを双方向にバインドしています。

<button>に対して、v-on:click="addTask"でクリック時のイベントハンドラを設定します。
こちらの記述で「Add」ボタンをクリックした際に、addTaskメソッドが実行されます。

<li>のv-forディレクティブを利用して、todosから繰り返しtodoを取得して、リスト表示しています。

<div id="app">
    <h2>TODO APP</h2>
    <form v-on:submit.prevent>
         <input type="text" v-model="newTask" />
         <button v-on:click="addTask">Add</button>
    </form>
    <ul>
        <li v-for="(todo, index) in todos">
            <span>{{ todo.item }}</span>
        </li>
    </ul>
</div>

TODOの完了・未完了の切り替え処理

続いて、TODOの完了・未完了を切り替える処理を実装します。

まず、addTaskのtodoにisCompletedを追加し、初期値をfalseとしておきます。

new Vue({
  el: '#app',
  data: {
    newTask: '',
    todos: []
  },
  methods: {
    addTask: function (event) {
      if(this.newTask === '') return

      let todo = {
        item: this.newTask,
        isCompleted: false
      }

      this.todos.push(todo)
      this.newTask = ''
    }
  }
})

index.htmlにチェックボックスを追加します。
v-modelを利用して、todo.isComletedと双方向にデータバインディングします。
チェックボックスにチェックを入れた場合は、isComletedがtrueになり、チェックを外した場合はisComletedがfalseとなります。

また、isCompletedがtrueに変わった際に、<span>のclass属性にcompletedをつけるようにします。
これで、チェック済みのtodoに取り消し線が表示されるようになります。
取り消し線については、main.cssで調整を行います。

<div id="app">
    <h2>TODO APP</h2>
    <form v-on:submit.prevent>
         <input type="text" v-model="newTask" />
         <button v-on:click="addTask">Add</button>
    </form>
    <ul>
        <li v-for="(todo, index) in todos">
            <input type="checkbox" v-model="todo.isCompleted">
            <span v-bind:class="{ completed: todo.isCompleted }">{{ todo.item }}</span>
        </li>
    </ul>
</div>
#app ul {
  list-style: none;
}

#app li > span.completed {
  text-decoration: line-through;
}

TODOの削除処理

最後に削除の処理を実装します。「Delete」ボタンを追加して、ボタンクリック時にtodos配列から該当のtodoを削除します。

main.jsのmethodsにdeleteTaskメソッドを追加します。
deleteTaskでは引数にtodos配列のindexを渡して、todosから該当indexのtodoを削除しています。

new Vue({
  el: '#app',
  data: {
    newTask: '',
    todos: []
  },
  methods: {
    addTask: function (event) {
      if(this.newTask === '') return

      let todo = {
        item: this.newTask,
        isCompleted: false
      }
      this.todos.push(todo)
      this.newTask = ''
    },
    deleteTask: function (index) {
      this.todos.splice(index, 1)
    }
  }
})

<button>を追加して、v-on:clickを用いてイベントハンドラに上記で定義した、deleteTaskを指定します。

<div id="app">
    <h2>TODO APP</h2>
    <form v-on:submit.prevent>
         <input type="text" v-model="newTask" />
         <button v-on:click="addTask">Add</button>
    </form>
    <ul>
        <li v-for="(todo, index) in todos">
            <input type="checkbox" v-model="todo.isCompleted">
            <span v-bind:class="{ completed: todo.isCompleted }">{{ todo.item }}</span>
            <button v-on:click="deleteTask(index)">Delete</button>
        </li>
    </ul>
</div>

まとめ

以上で、TODOアプリの実装は完了です。

最後に完成形のコードを記載しておきます。
エラーがでたり、うまく動かないという方はこちらのコードを参考に修正してください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/main.css">
</head>
<body>
    <div id="app">
        <h2>TODO APP</h2>
        <form v-on:submit.prevent>
            <input type="text" v-model="newTask" />
            <button v-on:click="addTask">Add</button>
        </form>
        <ul>
            <li v-for="(todo, index) in todos">
                <input type="checkbox" v-model="todo.isCompleted">
                <span v-bind:class="{ completed: todo.isCompleted }">{{ todo.item }}</span>
                <button v-on:click="deleteTask(index)">Delete</button>
            </li>
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
    <script src="js/main.js"></script>
</body>
</html>
new Vue({
  el: '#app',
  data: {
    newTask: '',
    todos: []
  },
  methods: {
    addTask: function (event) {
      if(this.newTask === '') return

      let todo = {
        item: this.newTask,
        isCompleted: false
      }
      this.todos.push(todo)
      this.newTask = ''
    },
    deleteTask: function (index) {
      this.todos.splice(index, 1)
    }
  }
})
#app ul {
  list-style: none;
}

#app li > span.completed {
  text-decoration: line-through;
}

TODOアプリは非常にシンプルなアプリですが、実践でも利用できるエッセンスが詰まっているので、ぜひ参考にしてみてください。

この記事が、「面白いな」、「勉強になったな」という方は、ぜひ、SNSでシェアしていただけると嬉しいです。