Vue CLIでシンプルなTodoリストを作成するハンズオン①

## はじめに

Vue CLIを使って、シンプルなTodoを作成しながらVuejsを理解するサンプルが欲しくて作成しながらメモしたことを残しておきます。

前提として、以下の素晴らしい記事の内容を終えている前提で、以下の記事で紹介されているシンプルなTodoをVueCLIで作っていくという内容になります。

1. [Vue.jsミニハンズオン（TODOリスト作成）](https://qiita.com/moonglows76/items/358ef3cd1566c38ece3a)
2. [Vue.jsミニハンズオン（TODOリストにアニメーションをつける）](https://qiita.com/moonglows76/items/d550f0243319d7111ee3)
3. [Vue.jsミニハンズオン（TODOリストをコンポーネント化する）](https://qiita.com/moonglows76/items/242aaf265672797c6800)

自分が色々試した結果上記記事がとてもわかりやすく最短であると感じました。
作成いただいた @moonglows76 さんありがとうございます！！！

煩わしいことは書かず、とりあえずVueを触ってみる程度の温度感ですので、悪しからず...

## 環境

* Nodejs 11.1.0
* VueCLI 4.0.5
* Yarn 1.19.2

## アプリケーションの作成

```
$ vue create vue-cli-todo-app

$ cd vue-cli-todo-app
```

作成と同時に以下のファイルを削除します。

* `src/components/HelloWorld.vue`
* `assets/logo.png`


`src/App.vue`を以下のように編集します。


```vue
<template>
  <div>
    <h1>Hello World</h1>
  </div>
</template>

<script>

export default {
  
}
</script>
```

ここまで編集したら、以下を実行して、ローカルサーバーを立ち上げましょう。

```
$ yarn serve

// yarnをインストールしていない場合はnpmでも起動できます。
$ npm run serve
```

起動されたら、[http://localhost:8080/](http://localhost:8080/)にアクセスすると、HelloWorldがブラウザに表示されるかと思います。

## データバインディングを体験する

`src/App.vue`を以下のように変更します。

```vue
<template>
  <div id="app">
    <h1>MyTodoApp</h1>
    <div>
      <label for="title">タイトル：</label>
      <input type="text" v-model="title" placeholder="タイトル">
    </div>
    <div>
      <label for="body">内容：</label>
      <textarea v-model="body" placeholder="内容"></textarea>
    </div>
    <pre>
      {{$data}}
    </pre>
  </div>
</template>

<script>

export default {
  data: () => ({
    title: '',
    body: ''
  }),
};
</script>
```

以下のような `pre` は視覚的にdataの中身を見やすいように配置しています。

```vue
<pre>
  {{$data}}
</pre>
```

## リストを作成する

`src/App.vue`を以下のように変更して、ブラウザを確認する。

```vue
<template>
  <div id="app">
    <h1>MyTodoApp</h1>
    <div>
      <label for="title">タイトル：</label>
      <input type="text" v-model="title" placeholder="タイトル">
    </div>
    <div>
      <label for="body">内容：</label>
      <textarea v-model="body" placeholder="内容"></textarea>
    </div>
    <ul v-for="list in lists">
      <li>{{ list.title }}</li>
    </ul>
    <pre>
      {{$data}}
    </pre>
  </div>
</template>

<script>

export default {
  data: () => ({
    lists: [
      {title: 'mytodo', body: 'contentcontent'}
    ]
  }),
};
</script>
```

すると、`Elements in iteration expect to have 'v-bind:key' directives`のようにエラーが表示されるかと思います。

`v-for` ディレクティブでは処理する配列に対して一意なユニークキーを設定しなければいけないというルールがあるからです。

以下のように変更しましょう。

```vue
<template>
  <div id="app">
    <h1>MyTodoApp</h1>
    <div>
      <label for="title">タイトル：</label>
      <input type="text" v-model="title" placeholder="タイトル">
    </div>
    <div>
      <label for="body">内容：</label>
      <textarea v-model="body" placeholder="内容"></textarea>
    </div>
    <ul v-for="(list, i) in lists" v-bind:key="i">
      <li>id: {{i}}, title: {{ list.title }}</li>
    </ul>
    <pre>
      {{$data}}
    </pre>
  </div>
</template>

<script>

export default {
  data: () => ({
    lists: [
      {title: 'mytodo', body: 'contentcontent'},
      {title: 'hogehoge', body: 'yuuyuuyuyuyu'}
    ]
  }),
};
</script>
```

v-forで配列のindexを取得するように変更し、エラーは解消されました。


## Todoを追加出来るようにする

`src/App.vue`を変更します。


```vue
<template>
  <div id="app">
    <h1>MyTodoApp</h1>
    <div>
      <label for="title">タイトル：</label>
      <input type="text" v-model="title" placeholder="タイトル">
    </div>
    <div>
      <label for="body">内容：</label>
      <textarea v-model="body" placeholder="内容"></textarea>
    </div>
    <div>
      <input type="submit" value="追加" @click="addList">
    </div>
    <ul v-for="(list, i) in lists" v-bind:key="i">
      <li>id: {{i}}, title: {{ list.title }}</li>
    </ul>
    <pre>
      {{$data}}
    </pre>
  </div>
</template>

<script>

export default {
  data: () => ({
    lists: [
      {title: 'mytodo', body: 'contentcontent'},
      {title: 'hogehoge', body: 'yuuyuuyuyuyu'}
    ],
    title: '',
    body: ''
  }),
  methods: {
    // Todoの追加
    addList: function(){

      this.lists.push({title: this.title, body: this.body})
      this.title = ''
      this.body = ''
    }
  }
};
</script>
```

`<input type="submit" value="追加" @click="addList">` をテキストエリアの下に追加して、サブミットボタンを作成します。

そして、以下で実行するメソッドを作成します。

```vue
methods: {
  // Todoの追加
  addList: function(){
   // 簡易なバリデーション
   if(this.title === '' || this.body === '') return

    // dataオプションのlists配列にv-modelから取得したデータを追加する
    this.lists.push({title: this.title, body: this.body})
    // 追加し終わったら、フォームのvalueを空にする
    this.title = ''
    this.body = ''
  }
}
```

## 削除メソッドを追加する

`src/App.vue`を以下のように変更します。

```vue
<template>
  <div id="app">
    <h1>MyTodoApp</h1>
    <div>
      <label for="title">タイトル：</label>
      <input type="text" v-model="title" placeholder="タイトル">
    </div>
    <div>
      <label for="body">内容：</label>
      <textarea v-model="body" placeholder="内容"></textarea>
    </div>
    <div>
      <input type="submit" value="追加" @click="addList">
    </div>
    <ul v-for="(list, i) in lists" v-bind:key="i">
      <li>id: {{i}}, title: {{ list.title }} <button @click="deleteList(i)">削除</button></li>
    </ul>
    <pre>
      {{$data}}
    </pre>
  </div>
</template>

<script>

export default {
  data: () => ({
    lists: [
      {title: 'mytodo', body: 'contentcontent'},
      {title: 'hogehoge', body: 'yuuyuuyuyuyu'}
    ],
    title: '',
    body: ''
  }),
  methods: {
    // リストの追加
    addList: function(){
      if(this.title === '' || this.body === '') return

      this.lists.push({title: this.title, body: this.body})
      this.title = ''
      this.body = ''
    },
    // リストの削除
    deleteList: function(i){
      this.lists.splice(i,1)
    }
  }
};
</script>
```

`li` 要素内に `button`を追加し削除出来るボタンを配置し、methodsにdeleteListを追加しています。
削除を行うときに、ロジックとしては配列からn番目の要素を削除するというロジックで実装するため、メソッドに対して、配列のindex番号を渡してあげる必要があります。

なので、`<li>id: {{i}}, title: {{ list.title }} <button @click="deleteList(i)">削除</button></li>` の `@click="deleteList(i)"` でdeleteListメソッドの引数にi（インデックス番号）を渡しています。

引数で渡されたインデックス番号を使用して、spliceで配列から除外したら削除機能は完成です。

完成形はこちらに置いておきます。
https://github.com/ken-sayama/vue-cli-todo-app