😀

はじめてのVue.jsでToDoリスト作ってみた

2021/06/19に公開

#はじめに

本記事はVue.jsの初心者向けにToDoリストを用いてVue.jsの基本の書き方を解説するためのものです。

以下、ToDoリストの機能です。

・タスク追加機能
・タスク削除機能
・タスクの有無による画面の切り替え
・タスク数カウント機能
・リアルタイム検索機能

#動作

Videotogif.gif

#成功コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="app">
      <div>残タスク数:{{ remaining }}/{{ list.length }}</div>
      <input type="text" v-model="addText" placeholder="ToDoを入力して">
      <button v-on:click="addToDo()">追加</button>
      <button v-on:click="deleteBtn()">削除</button></br>
      <input type="text" placeholder="キーワードを入力して" v-model="keyword"><hr>
      <div v-if="keyword !== '' ">
        <ul  v-for="list in filteredLists">
          <li>
            <span v-bind:class="{ done: list.isChecked }">
              <input type="checkbox" v-model="list.isChecked">{{ list.text }}
            </span>
          </li>
        </ul>
      </div>
      <div v-else>
        <div v-if="list.length">
          <ul v-for="todo in list">
            <li>
              <span v-bind:class="{ done: todo.isChecked }">
                <input type="checkbox" v-model="todo.isChecked">{{ todo.text }}
              </span>
            </li>
          </ul>
        </div>
        <div v-else>
          現在タスクはありません
        </div>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="main.js"></script>
    
</body>
</html>
main.js
var app = new Vue({
  el: '#app',
  data: {
      list: [],
      addText: '',
      keyword: ''
  },

  //watchでlistの変更を監視
  watch: {
      list: {
          handler: function() {
              //localStorageにデータを保存
              localStorage.setItem("list", JSON.stringify(this.list));
          },
          deep: true
      }
  },
  //マウントされた時にlocalStorageからデータを取得
  mounted: function() {
      this.list = JSON.parse(localStorage.getItem("list")) || [];
  },
  methods: {
      addToDo: function() {
          if (this.addText !== '') {
              this.list.push({
                  text: this.addText, 
                  isChecked: false,
              });
          }
          this.addText = '';
      },

      deleteBtn: function() {
          this.list = this.list.filter(function(todo) {
              return !todo.isChecked;
          });
      },

      filterLists: function () {
        var filtered = [];
        for (var i in this.list) {
            var list = this.list[i];
            var text = this.addText
            if (list.text.indexOf(this.keyword) !== -1) {
                filtered.push(list);
            }
         }
        return filtered;
      }
  },

  computed: {
    remaining: function() {
        var count = 0;
        var todos = this.list;
        var length = todos.length;
        for(var i = 0; i < length; i++) {
            if(!todos[i].isChecked) {
                count++;
            }
        }
        return count;
    },

    filteredLists: function () {
        return this.filterLists();
    }
    
}
});

#解説

それでは、1つずつ解説していきたいと思います。

###タスク入力・追加・削除機能
これはHTMLでいうとinputタグbuttonタグの部分に当たります。

index.html
<input type="text" v-model="addText" placeholder="ToDoを入力して">
<button v-on:click="addToDo()">追加</button> 
<button v-on:click="deleteBtn()">削除</button>

まず、タスクの入力についてですが、v-modelを利用してVue.js側でデータが更新されるようにします。
この設定を行うことでbuttonタグの、v-onディレクティブに設定している各関数の処理を起動することができます。

次に追加・削除機能ですが、こちらはv-onディレクティブでクリックされた時にイベント発火するようにしています。
そして、クリックイベントが起きた時、各機能で追加・削除するための関数を呼び出しています。

関数の中身は、このような感じです。

main.js
addToDo: function() {
   if (this.addText !== '') {
     this.list.push({
      text: this.addText, 
      isChecked: false,
    });
  }
  this.addText = '';
},

deleteBtn: function() {
   this.list = this.list.filter(function(todo) {
      return !todo.isChecked;
   });
}

ちなみに、dataの部分についは以下のように定義しました。

main.js
data: {
      list: [],
      addText: '',
      keyword: ''
  },

まず、addToDoについてですが、これは条件分岐で、入力欄にテキストが入力された時にlist配列にチェックボックスが空の値を格納するという動きをします。
そして、忘れがちなのが、最後の行のthis.addText = '';です。

これは、タスクを追加したあとに入力欄に入力された文字がそのまま残っているという状態を防ぐ働きをします。

次にdeleteBtnですが、ここでのポイントはfilter()です。
filterの基本的な記述は以下のように記述します。

var items = 配列データ;
 
items.filter( コールバック関数 )

そして、filterの気になる役割ですが、これは特定の条件を与えて配列データで取得したい内容を「コールバック関数」に書くことで、任意のデータを抽出して新しい配列を生み出すという役割があります。

つまり、ここではToDoリストのチェックボックスにチェックが入っているリストを抽出し、新しいthis.listという配列に格納しているということです。

これらをHTML側で呼び出しイベント発火することで、タスクの入力から追加・削除までの機能を実装することができます。

###タスクの表示・タスクの有無による画面の切り替え

タスクの表示と画面の切り替えは、以下のコードです。

index.html
<div v-if="list.length">
   <ul v-for="todo in list">
      <li>
        <span v-bind:class="{ done: todo.isChecked }">
          <input type="checkbox" v-model="todo.isChecked">{{ todo.text }}
        </span>
      </li>
   </ul>
</div>
<div v-else>
  現在タスクはありません
</div>

まずToDoリストが格納させている配列の一覧を表示させたいのでulタグv-forディレクティブを指定して配列データを表示するよう指示してあげます。

次にspanタグv-bindディレクティブを指定して、完了したタスクにCSSのクラスを付与するようにします。
クラス名はdoneで、todoがチェックされている時にCSSが適用されるように指定します。

つまり、チェックが入るとCSSにより打消し線が入るようにしているという仕様になっているわけです。

inputタグではチェックボックスを作成しています。こちらではv-modelディレクティブを使用し、Vue.js側でチェック判定のデータのやり取りをするようにしています。

最後に{{ todo.text }}とすることで、Vue.jsからリストに格納されているtextを表示することができます。

そして、ToDoの有無による条件分岐としてv-if,v-elseディレクティブを使用し表示を切り替えます。

条件式としてはv-if="list.lengthとし配列の中身があるかどうかを判定しています。

###タスクカウント機能

こちらは総数は、そのまま配列の合計数を表示するだけでいいので、{{ list.length }}としますが、残数はチェックされているかの計算が必要になるので、算出プロパティとして処理を値として出力するようにします。

main.js
computed: {
    remaining: function() {
        var count = 0;
        var todos = this.list;
        var length = todos.length;
        for(var i = 0; i < length; i++) {
            if(!todos[i].isChecked) {
                count++;
            }
        }
        return count;
    }

まず、配列と配列の合計数をtodoslengthという変数に代入します。

そしてfor文を使用し、配列の合計が0より大きい時にcountを増加させるようにし、最後に戻り値としてカウントを返すようにします。

このremainingという値を、あとはHTML側で`{{ remaining }}として呼び出すだけでOKです。

###リアルタイム検索機能

いよいよ、最後の機能です。
もう少しですので、がんばりましょう!!

正直、僕はここに一番手こずりました。

以下、コードです。

index.html
<input type="text" placeholder="キーワードを入力して" v-model="keyword"><hr>
<div v-if="keyword !== '' ">
  <ul  v-for="list in filteredLists">
    <li>
      <span v-bind:class="{ done: list.isChecked }">
        <input type="checkbox" v-model="list.isChecked">{{ list.text }}
      </span>
     </li>
   </ul>
</div>
<div v-else>
  <!-- 通常のToDoリストの表示 -->
</div>
main.js

data: {
      list: [],
      addText: '',
      keyword: ''
  },

methods: {

      filterLists: function () {
        var filtered = [];
        for (var i in this.list) {
            var list = this.list[i];
            var text = this.addText
            if (list.text.indexOf(this.keyword) !== -1) {
                filtered.push(list);
            }
         }
        return filtered;
      }
  },

computed: {

    filteredLists: function () {
        return this.filterLists();
    }
    
  }

まず検索ワードの入力欄ですが、こちらはToDoの入力欄と同じようにv-modeldatakeywordを指定することで、Vue.js側でデータ(keywordの値)の更新が行われるようにします。

そしてv-if,v-elseを使用し検索しているときと、そうでない時で表示を切り替えます。

ですので、条件式としてはv-if="keyword !== '' "とすることで、検索欄に入力がある時、検索の処理をするという分岐を作ることができます。

次に検索されたリストの表示ですが、配列の表示で使うv-forを利用します。

ここでは算出プロパティで定義しているfilteredListsの中の配列を表示したいので、v-for="list in filteredLists"と定義します。

肝心のfilteredListsの内容ですが、戻り値としてfilterLists()メソッドを出力するようにしています。
つまり、この検索機能で最も重要となるのは、filterLists()メソッドということです。

それでは、そのメソッドの内容を見ていきましょう。

main.js
filterLists: function () {
        var filtered = [];
        for (var i in this.list) {
            var list = this.list[i];
            var text = this.addText;
            if (list.text.indexOf(this.keyword) !== -1) {
                filtered.push(list);
            }
         }
        return filtered;
      }

上記がそのメソッドの中身です。

まず、通常表示のToDoリストの配列とは別に検索した時用の配列を定義する必要があります。

そして、for-in文を使い、dataに定義されているlist配列の中身を取り出します。

そして、通常のToDoリストの中身と、ToDo入力欄の値を各変数に代入し、if文で検索にかけます。

今回は文字列の検索をしたいのでindexOf()を使用します。
そして、検索したい内容の指定ですが、ToDoのテキストを検索にかけたいので、if文の上で定義した変数を使用して条件とします。

※なんで!== -1なのかはわかりませんでしたが、このような書き方をした記事を多数見たので、調べたい人は調べてください。
筆者は調べることを諦めました。。。

はい。ということで、条件に合致したときのことを解説します。

条件に合致すると、見ての通り合致した値を検索用に定義している配列に格納するようにしています。

そして、最後に戻り値として検索用の配列を指定してあげることで、検索欄に入力が合った時に、合致するものを表示することがデキるようになります。

ここで、「記述を減らしたいなら、このメソッドだけにして算出プロパティでこのメソッドをわざわざ呼び出さなくても良いのでは?」
と思う人もいますが、

今回の仕様ではリアルタイムでの検索なので双方向データバインディングにして検索欄に入力が合ったときだけ処理を実行したいので算出プロパティとして出力するようにしています。

この辺は難しいですが、メソッドだと関係ない箇所で画面に描画が入ると見た目では処理が動作しなくても裏で処理が動作しているので、無駄に読み込みが遅くなったりします。

しかし、算出プロパティなら、そのようなことが起きず、こちらが指定した場所に描画があったときだけ処理をリアルタイムで実行することができるので、それぞれの違いを理解し仕様に合わせた使い分けが必要です。

それでは、今回の解説は以上です。
Vue.jsは初心者の方には難しいとは思いますし、僕自身も初心者なので、こんな簡単なアプリでも結構、大変でした。

少しでも参考になれば幸いです。

Discussion