Vue.jsの基礎をまなぼう！

##あいさつ
初めての人は初めまして！知っている人はこんにちは！
中学生バックエンドPGのAtieです！
今回はVue.jsについて学んできたのでアウトプットします！
「え？バックエンドがVue.jsをなんで勉強してるの？」
これはフレームワークに慣れておくためとSPAを開発したかったからです
実際Vue.jsを学んだ感想は少し難しかったです
しかしコードがシンプルだったので読みやすく書きやすかったです
では！

##環境構築
まずはVue.jsの環境構築をしていきます
まずは必要なファイルを作っていきます

- main.js
- index.html
- style.css

この３つのファイルを作ってください

次にVue.jsを使えるようにします

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
Vue.jsを使うには以下の１行を追加するだけで使えるようになります
便利で簡単ですね！

```index.html
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
```
この行をhtmlのbodyタグの最後の行に入れます

###双方向データバインディング
Vue.jsには「双方向データバインディング」ができるという特徴があります
双方データバインディングですがデータバインディングとはUIとデータを結びつけるという意味で双方向というのはdataを更新すればUIが更新されて逆にUIが更新されればdataが更新されるという意味です
たとえばTwitterを例に挙げてみましょう
Twitterのハートの部分を押すと色がピンクになりハートの数が表示されるようになれます
これはUI(ハートの部分)が更新されることでデータ(ハートの数)が更新されています
この双方向データバインディングができることでSPAが実現可能です

では実際にしてみましょう
まずはVue.jsで制御する要素を作ります

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
divタグにmainというidを付けました
今回はこのmainという要素の中をVue.jsで制御していきます
次にjsを書いていきます
まずは即時関数でエラーチェックをしてjsからmainを使えるようにします

```mian.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main'
    })
})();
```
UIに結び付くモデルはよくView Modelと言われているので略してvmとしました
そしてnewでVueオブジェクトを作成します
どの領域かを結びつけるかをelementsの略であるelで指定します
cssのように#をつけてidを指定します
これでjsから扱えるようになりました
ではこのモデルにdataを保持してもらいます
dataのなかにnameというキーでAtieという値を保持してもらいます

```main.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            comment: "name",
        }
    })
})();
```
では表示させてみます
表示するには二重波カッコで囲う必要があります

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <p>{{ name }}</p>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
これで値が表示されてます

次はUIからデータに変更できるようにしてみます
inputたぐにv-model="name"とすることでnameと入力フォームが結びつくようになります
即時に変更されていることがわかります

この二重波カッコですがjsの式をそのまま書くことができます
たとえば入力された文字を大文字にするためにname.toUpperCaseと書くことができます

##ToDoListアプリを作る
ではVue.jsの基礎を抑えたのでToDoListアプリを作っていきます
まずはToDoListを保存する配列を作ります
todosというキーで配列を保存します

```mian.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            todos: [
                'todo 1',
                'todo 2',
                'todo 3'
            ],
        }
    })
})();
```
次にhtmlに反映させますがただ単にliタグの中身を二重波カッコで囲てしまうと追加や削除するときにリロードを挟んでしまうので挟まないようにliタグを配列のループでその数だけ表示するようにします
そのためにはv-forでv-for="todo in todos"とします
こうすることでtodoにtodosの値が一つずつ入っていきその分ループします
そしてliタグの中身をtodoの値を表示するようにすればループされながら配列の中身が一つずつ表示されるようになります
これで表示する仕組みが整いました

ちなみにですがv-のように始まる特殊な属性をディレクティブと呼びます

次にToDoの追加をできるようにします
そのためにformを追加します

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <li v-for="todo in todos">{{ todo }}</li>
            </ul>
            <form>
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
v-modelを使って結び付けていきます
submitされた時のイベントを設定していきます
イベントを設定するにはv-onとする必要があります
v-onはよく使うので@と略することができます

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <li v-for="todo in todos">{{ todo }}</li>
            </ul>
            <form v-on:submit="addTodo">
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```

ではsubmitされた時のイベントを設定していきます
methodsというキーにメソッドを設定していきます
data内のデータにはthisでアクセスすることができます

```main.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            newItem: '',
            todos: [
                'todo 1',
                'todo 2',
                'todo 3'
            ],
            methods: {
                addTodo: function() {
                    this.todos.push(this.newItem);
                },
            },
        },
    })
})();
```
このように書くことができます
しかしこのままではformがsubmitされてページが移行してしまうのでうまくいいきません
これを防ぐためには@submit.preventとすることで防ぐことができます

これでうまくいきます

追加した後にフォームに値が残ってしまうのでnewItemを空にしておきます

```main.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            newItem: '',
            todos: [
                'todo 1',
                'todo 2',
                'todo 3'
            ],
            methods: {
                addTodo: function() {
                    this.todos.push(this.newItem);
                    this.newItem = '';
                },
            },
        },
    })
})();
```

###ToDoの削除
次にToDoの削除をできるようにしていきます
原理としてはtodosの配列から削除すればいいので簡単です

spanでxを作っていきます

この場合todosの何番目を削除すればいいのかわからないのでこのようにしてindexに数字が入るようにします

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <li v-for="(todo, index) in todos">{{ todo }} <span @click="deletItem">[X]</span></li>
            </ul>
            <form @submit.prevent="addTodo">
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
では削除するメソッドを作っていきます
といっても簡単ですspliceでindex番目から1番目を消すだけです
簡単簡単♪

```main.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            newItem: '',
            todos: [
                'todo 1',
                'todo 2',
                'todo 3'
            ],
            methods: {
                addTodo: function() {
                    this.todos.push(this.newItem);
                    this.newItem = '';
                },
                deletItem: function(index) {
                    this.todos.splice(index, 1);
                    this.newItem = '';
                },
            },
        },
    })
})();
```
これで削除の仕組みができました

###完了状態を管理する
では次に完了状態を管理できるようにします
完了状態を管理するためにtodosをオブジェクトにしてtitleとisDoneで管理します

```main.js
(function () {
    'use strict';
    const vm = new Vue({
        el: '#main',
        data: {
            newItem: '',
            todos: [{
                title: 'task 1',
                isDone: false
            }, {
                title: 'task 2',
                isDone: false
            }, {
                title: 'task 3',
                isDone: true
            }],
            methods: {
                addTodo: function() {
                    this.todos.push(Item);
                    this.newItem = '';
                },
                deletItem: function(index) {
                    this.todos.splice(index, 1);
                    this.newItem = '';
                },
            },
        },
    })
})();
```
ただこのままでは表示がおかしくなるのでhtmlも変えます

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <li v-for="(todo, index) in todos">{{ todo.title }} <span @click="deletItem">[X]</span></li>
            </ul>
            <form @submit.prevent="addTodo">
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
次に完了状態を可視化していきます

まずは以下のようにhtmlを変えます

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <li v-for="(todo, index) in todos">{{ todo.title }} <span @click="deletItem">[X]</span></li>
            </ul>
            <form @submit.prevent="addTodo">
                <input type="checkbox" v-model="todo.isDone">
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
次にisDoneの状態によってチェックの表示状態を変えたいのですが...
便利なことにisDoneがtrueの時にチェックがついてくれます

チェックがついた項目はdoneというclassをつけてあげてあげます
データにおうじてclassを付け替えるにはv-bind:classをつけます
v-bindもv-onと同じくよく使われるので:で略してつけることができます

```main.js
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <div id="main">
            <ul>
                <input type="checkbox" v-model="todo.isDone">
                <span :class="{done :todo.isDone}"
                <li v-for="(todo, index) in todos">{{ todo.title }}<span @click="deletItem">[X]</span>
                </li>
            </ul>
            <form @submit.prevent="addTodo">
                <input type="text" v-model="newTodo">
                <input type="submit" value="Add Todo">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```

次にToDoListがなかったらなにか表示させてみましょう
そのためには条件分岐をする必要があるのですが条件分岐をするにはv-ifというディレクティブがあります
今回はToDoListがなかったら「No todolist」と表示させます
通常ならいつもどおりliのところに入れて条件分岐させるのですがv-ifとv-forではv-forのほうが優先されてしまうのでulのほうに書きます

```main.js
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <div id="app" class="container">
            <h1>My Todos</h1>
            <ul v-if="todos.length">
                <li v-for="(todo, index) in todos">
                <input type="checkbox" v-model="todo.isDone">
                <span :class="{done: todo.isDone}">{{ todo.title }}</span>
                <span @click="deleteItem(index)" class="command">[x]</span>
                </li>
            </ul>
            <ul v-else>
                <p>No todolist</p>
            </ul>
            <form @submit.prevent="addItem">
                <input type="text" v-model="newItem">
                <input type="submit" value="Add">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
todos.lengthがtrueだったらv-ifの処理が実行されてv-elseの場合はメッセージを表示するようになっています
v-ifを使わないで書く書き方がありますがそっちのほうがすっきりしているしているのでそちらを使います
v-showというディレクティブです
v-showはv-ifと同じように条件を入れます

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <div id="app" class="container">
            <h1>My Todos</h1>
            <ul>
                <li v-for="(todo, index) in todos">
                <input type="checkbox" v-model="todo.isDone">
                <span :class="{done: todo.isDone}">{{ todo.title }}</span>
                <span @click="deleteItem(index)" class="command">[x]</span>
                </li>
                <li v-show="!todos.length">No todos</li>
            </ul>
            <form @submit.prevent="addItem">
                <input type="text" v-model="newItem">
                <input type="submit" value="Add">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
今回はtodos.lengthがfalseの時に実行してほしいので!を使ってnot論理回路にしました

###ToDoListの数を表示する
次にToDoListの数と終わった数を表示させます
Vue.jsは算術プロパティを使うことができるので使っていきます

computedというキーを使ってデータから動的にプロパティを計算してくれる算術プロパティがあるので使っていきます
remainingとしてあげてデータから自動的にremainingを算出してプロパティにしてあげます
今回はisDoneがfalseの項目を調べたいのでjsのfilterという命令を使ってみます
filter関数を引数に取るのでfunction(todo)としつつtodoのisDoneがfalse
つまり残っているタスクをreturnすればそれをフィルターしてitemにまだ終わっていないタスクを入れていけばわかります
今回まだ終わってないタスクの件数を調べたいのでlengthを返してあげればremainingにはisDoneがfalseの件数はいってくるはずです

```main.js
(function() {
    'use strict';
    var vm = new Vue({
        el: '#app',
        data: {
            newItem: '',
            todos: [{
                title: 'task 1',
                isDone: false
            }, {
                title: 'task 2',
                isDone: false
            }, {
                title: 'task 3',
                isDone: true
            }]
        },
        methods: {
            addItem: function() {
                var item = {
                    title: this.newItem,
                    isDone: false
                };
                this.todos.push(item);
                this.newItem = '';
            },
            deleteItem: function(index) {
                this.todos.splice(index, 1);
            },
            computed: {
                remaining: function() {
                    const items = this.todos.filter(function() {
                        return !todo.isDone;
                    });
                    return items.length;
                }
            }
        }
    });
})();
```
少しややこしいですがよく見るとただitemsにtodo.isDoneがfalseのを入れていてその下でitemsの数を返しています

```main.js
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <div id="app" class="container">
            <h1>
                My Todos
                <span class="info">({{ remaining }} / {{ todos.length }})</span>
            </h1>
            <ul>
                <li v-for="(todo, index) in todos">
                <input type="checkbox" v-model="todo.isDone">
                <span :class="{done: todo.isDone}">{{ todo.title }}</span>
                <span @click="deleteItem(index)" class="command">[x]</span>
                </li>
                <li v-show="!todos.length">No todos</li>
            </ul>
            <form @submit.prevent="addItem">
                <input type="text" v-model="newItem">
                <input type="submit" value="Add">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```
あとはspanタグで表示できるようにしておきました

###完了したタスクの一括削除
次に完了したタスクの一括削除をしていきます
purgeというボタンをつけて@clickでpurgeという処理をさせます

purgeというメソッドを作っていきます
まずthis.todosにremainingを割り当てることで終わった数ではなくて終わった配列そのものを返すようにします
remainingを表示するところも直す必要があります

```main.js
(function() {
    'use strict';
    var vm = new Vue({
        el: '#app',
        data: {
            newItem: '',
            todos: [{
                title: 'task 1',
                isDone: false
            }, {
                title: 'task 2',
                isDone: false
            }, {
                title: 'task 3',
                isDone: true
            }]
        },
        methods: {
            addItem: function() {
                var item = {
                    title: this.newItem,
                    isDone: false
                };
                this.todos.push(item);
                this.newItem = '';
            },
            deleteItem: function(index) {
                this.todos.splice(index, 1);
            },
            computed: {
                remaining: function() {
                    return this.todos.filter(function(todo) {
                        return !todo.isDone;
                    })
                }
            },
            purge: function() {
                if (!confirm('delet finished?')) {
                    return;
                }
                this.todos = this.remaining;
            }
        }
    });
})();
```

```index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>My Vue App</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <div id="app" class="container">
            <h1>
                My Todos
                <span class="info">({{ remaining.length }} / {{ todos.length }})</span>
            </h1>
            <ul>
                <li v-for="(todo, index) in todos">
                <input type="checkbox" v-model="todo.isDone">
                <span :class="{done: todo.isDone}">{{ todo.title }}</span>
                <span @click="deleteItem(index)" class="command">[x]</span>
                </li>
                <li v-show="!todos.length">No todos</li>
            </ul>
            <form @submit.prevent="addItem">
                <input type="text" v-model="newItem">
                <input type="submit" value="Add">
            </form>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script src="js/main.js"></script>
    </body>
</html>
```

###LocalStrageでデータの永続化
次にLocalStorageでデータの永続化をしてみます

データの保存はtodosに変更が加えられたときに保存の処理を実行したいのでdeep watcherを使っていきます
watchだけだと配列の中身の変更まで監視してくれないのでdeepオプションをオンにするためにdeep: trueとする必要があります

```main.js
(function() {
    'use strict';
    var vm = new Vue({
        el: '#app',
        data: {
            newItem: '',
            todos: [{
                title: 'task 1',
                isDone: false
            }, {
                title: 'task 2',
                isDone: false
            }, {
                title: 'task 3',
                isDone: true
            }]
        },
        watch: {
            todos: {
                handler: function() {
                    localStorage.setItem('todos', JSON.stringify(this.todos));
                }
            },
            deep: true
        },
        methods: {
            addItem: function() {
                var item = {
                    title: this.newItem,
                    isDone: false
                };
                this.todos.push(item);
                this.newItem = '';
            },
            deleteItem: function(index) {
                this.todos.splice(index, 1);
            },
            computed: {
                remaining: function() {
                    return this.todos.filter(function(todo) {
                        return !todo.isDone;
                    })
                }
            },
            purge: function() {
                if (!confirm('delet finished?')) {
                    return;
                }
                this.todos = this.remaining;
            }
        }
    });
})();
```

データが保存できたので取り出してみます
this.todosに対してjsonデータをparseしつつlocalStrageからtodoのキーでデータをgetItemすればいいです
ついでにthis.todosの配列を空にしておきます

```main.js
(function() {
    'use strict';
    var vm = new Vue({
        el: '#app',
        data: {
            newItem: '',
            todos: []
        },
        watch: {
            todos: {
                handler: function() {
                    localStorage.setItem('todos', JSON.stringify(this.todos));
                }
            },
            deep: true
        },
        methods: {
            addItem: function() {
                var item = {
                    title: this.newItem,
                    isDone: false
                };
                this.todos.push(item);
                this.newItem = '';
            },
            deleteItem: function(index) {
                this.todos.splice(index, 1);
            },
            mounted: function() {
                this.todos = JSON.parse(localStorage.getItem('todos')) || [];
            },
            computed: {
                remaining: function() {
                    return this.todos.filter(function(todo) {
                        return !todo.isDone;
                    })
                }
            },
            purge: function() {
                if (!confirm('delet finished?')) {
                    return;
                }
                this.todos = this.remaining;
            }
        }
    });
})();
```
もし何も保存されていなければエラーになるのでそれを防ぐためになかった場合は空の配列を返すようにしました

これで一通りのVue.jsの基礎をまなべました

##最後に
Vue.jsの基礎をこの記事にギュッと詰め込みました！
コンポーネントはまた別の記事で解説します
最後まで読んでいただきありがとうございました！
Twitterしています！→[AtieのTwitter](https://twitter.com/Atie_BackendPG)
では！また次回の記事で！