Vuetifyで30分でTodoアプリを作るチュートリアル

# 初めに

はじめましてKouと申します。
仕事でVuetifyを使う機会があったので備忘録と共有を含めて簡単なTodoアプリを作る方法を紹介します！

これでVuetifyの便利さが伝わったらいいなという思いを込めていきます！

完成リポジトリ：https://github.com/KouWakai/VuetifyTodo

### 完成イメージ

これが大体30分くらいで作れます

![822e1060d6778fa606f510a0cb801a75.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/ba17f265-64d0-d81b-a415-77d7cf3ec988.png)

# このチュートリアルで必要なもの

Vue-cliとVuetifyのcliパッケージのみです。
次の環境構築でインストール方法は解説します。
nodejsはOSによって違うのと先人がわかりやすいやり方を解説してくださっているのでそちらでお願いいたします

以下リンクです。

・[nodejs](https://nodejs.org/ja/)
・[vue](https://jp.vuejs.org/v2/)
・[vuetify](https://vuetifyjs.com/ja/)

# 環境構築

自分で公式ドキュメント読んで、できる方は飛ばしちゃってOKです！

## vue cliのインストール
以下のコマンドでインストールしてください

```bash
$ npm install -g @vue/cli
```

## vueプロジェクト作成※必ずvue2を選択

せっかくなのでちょっといじりましょう。
manually select featuresを選択してください。

```bash
Vue CLI v4.5.15
? Please pick a preset: 
  todoapp ([Vue 2] dart-sass, babel, router, vuex) 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3) ([Vue 3] babel, eslint) 
❯ Manually select features 
```

Linter/Formatterはうるさいので消しておきましょう。
カーソルを矢印キーで動かしてスペースを押すと丸を消せます。

```bash
? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
❯◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
```

**ここは必ず2.xでお願いします！！**
 というのもVuetifyはvue3に対応していないためです。
vue3にしてnpm run serveするとコンパイルが通りません。ちょびハマりポイントなので注意

```bash
? Choose a version of Vue.js that you want to start the project with (Use arro
w keys)
❯ 2.x 
  3.x 
```

package.jsonでいきましょう。

```bash
? Where do you prefer placing config for Babel, ESLint, etc.? 
  In dedicated config files 
❯ In package.json 
```

nでいいです。一応、yにすると今回の選択をプリセットとして保存できます。

```bash
? Save this as a preset for future projects? (y/N) n
```

## Vuetify導入

cd todoappでvueプロジェクトのディレクトに入ります。

```bash
cd todoapp
```

そして以下のコマンドでVuetifyを導入します

```bash
vue add vuetify
```
Defaultでいきましょう。ということでEnterを押してください

```
? Choose a preset: (Use arrow keys)
  Configure (advanced) 
❯ Default (recommended) 
  Vite Preview (Vuetify 3 + Vite) 
  Prototype (rapid development) 
  Vuetify 3 Preview (Vuetify 3) 
```
こんな感じになったら終了です。環境構築お疲れ様でした！

```bash
✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
 vuetify  Discord community: https://community.vuetifyjs.com
 vuetify  Github: https://github.com/vuetifyjs/vuetify
 vuetify  Support Vuetify: https://github.com/sponsors/johnleider
```

# さあコーディングするぞ！
## 1.ワイヤーフレームを持ってくる

todoappをエディターで開いてください。この記事ではVSCODEを使います。

開いたらターミナルで以下のコマンドを打ってください

```
npm run serve
```

コンパイルが終わったら、http://localhost:8080/ を開きましょう。（8080があいてなかったら8081になったりします。）

![210757a7dcb5bc46304a9c28a8fc71a0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/ca883183-b76c-9ce8-effb-3219baf0aa16.png)

この画面になったらうまく環境構築がいってます。これがVuetifyをデフォルトのvueに導入した直後の状態です。

ここからロジックを追加してtodoappにしていきます。

todoapp/src/App.vue

を開いてください。v-appやらなんやら書いてますね。ここをいじっていきます。

ワイヤーフレームを使いましょう。以下のリンクでVuetify公式のワイヤーフレームのページにいってください。

https://vuetifyjs.com/ja/getting-started/wireframes/

Side navigationを使いましょう。以下のリンクかワイヤーフレームのページのside navigationをクリック

https://vuetifyjs.com/ja/examples/wireframes/side-navigation/

下のようなページが出てくるので右隅にあるアイコンをクリック！

![33162b6d0b4b66da54ae1f3a21ddeb4a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/4ea32f3f-189b-72c2-6030-32665827cff5.png)

そしたらgithubのリポジトリにさっきのページのソースが出てくるので全部コピー
![5ce91aea6184fae2862068cbcf331e84.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/615f212c-9be2-63d1-8937-c8bafb8c666b.png)

さっき開いた todoapp/src/App.vueに貼り付けてください。
もともとはいってたソースは消してください。

そしてlocalhost:8080ｄ（以下、開発サーバー）を開くとワイヤーフレームが表示されているはずです。

Todoapp/src/components/Todo.vueを作成

19行目あたりのv-main内のスコープに<Todo />を記入

```vue
    <v-main>
      <Todo />
    </v-main>
```

25行目から以下のコードでTodo.vueをimport、exportしてください。

```vue
<script>
import Todo from './components/Todo.vue'
  export default {
      components:{
          Todo,
      },
  }
</script>
```

Todo.vueを開いてtemplateとh1タグを書いて好きな文字を入力してください

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

開発サーバーを見ると下のようになっているはずです！

![fea10fd5b37723ae2fbc252c1626c437.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/d085fb6d-7843-7a5d-3eb6-1e30ecfaaa2c.png)

このTodo.vueにコーディングしていきましょう！

## 2.コンポーネントを駆使する

サイドバーのListsをクリック
![8e1d3d41603cbe85e70d9af61931c0af.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/a914d26c-4e11-f323-79a3-5511302bcdd1.png)

するとページの真ん中辺りにいい感じのAction stackがありますね。<>をクリックしてください

![081396fb2513eba3c1bc35b31b3b3ff7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/b1f7f978-c54b-4fba-8a36-edfa115870f3.png)

するとソースがでてくるので<tempalte></tempalte>をすべてコピー

```vue
<template>
  <v-card
    class="mx-auto"
    max-width="500"
  >
    <v-toolbar
      color="pink"
      dark
    >
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-toolbar-title>Inbox</v-toolbar-title>

      <v-spacer></v-spacer>

      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>

      <v-btn icon>
        <v-icon>mdi-checkbox-marked-circle</v-icon>
      </v-btn>
    </v-toolbar>

    <v-list two-line>
      <v-list-item-group
        v-model="selected"
        active-class="pink--text"
        multiple
      >
        <template v-for="(item, index) in items">
          <v-list-item :key="item.title">
            <template v-slot:default="{ active }">
              <v-list-item-content>
                <v-list-item-title v-text="item.title"></v-list-item-title>

                <v-list-item-subtitle
                  class="text--primary"
                  v-text="item.headline"
                ></v-list-item-subtitle>

                <v-list-item-subtitle v-text="item.subtitle"></v-list-item-subtitle>
              </v-list-item-content>

              <v-list-item-action>
                <v-list-item-action-text v-text="item.action"></v-list-item-action-text>

                <v-icon
                  v-if="!active"
                  color="grey lighten-1"
                >
                  mdi-star-outline
                </v-icon>

                <v-icon
                  v-else
                  color="yellow darken-3"
                >
                  mdi-star
                </v-icon>
              </v-list-item-action>
            </template>
          </v-list-item>

          <v-divider
            v-if="index < items.length - 1"
            :key="index"
          ></v-divider>
        </template>
      </v-list-item-group>
    </v-list>
  </v-card>
</template>
```

todo.vueに上書きします。
そして、scriptをクリックして<script></script>をすべてコピー。
![c4a3453bdd9ff1307deb88a00bd455d7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/db5fd8f1-eeb4-9531-c2c3-c68b77ea73c5.png)

```vue
<script>
  export default {
    data: () => ({
      selected: [2],
      items: [
        {
          action: '15 min',
          headline: 'Brunch this weekend?',
          subtitle: `I'll be in your neighborhood doing errands this weekend. Do you want to hang out?`,
          title: 'Ali Connors',
        },
        {
          action: '2 hr',
          headline: 'Summer BBQ',
          subtitle: `Wish I could come, but I'm out of town this weekend.`,
          title: 'me, Scrott, Jennifer',
        },
        {
          action: '6 hr',
          headline: 'Oui oui',
          subtitle: 'Do you have Paris recommendations? Have you ever been?',
          title: 'Sandra Adams',
        },
        {
          action: '12 hr',
          headline: 'Birthday gift',
          subtitle: 'Have any ideas about what we should get Heidi for her birthday?',
          title: 'Trevor Hansen',
        },
        {
          action: '18hr',
          headline: 'Recipe to try',
          subtitle: 'We should eat this: Grate, Squash, Corn, and tomatillo Tacos.',
          title: 'Britta Holt',
        },
      ],
    }),
  }
</script>
```

Todo.vueの74行目以降に貼り付けます。

すると開発サーバーが以下のようになります。

![836d1ba808bdc9f03e9e31f98c2b0ada.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/4b4b2adb-c167-05d2-4256-cc1f611d9ce1.png)

## 3.本当のコーディング
12行目のInboxをTodoに変えましょう

```vue
      <v-toolbar-title>Todo</v-toolbar-title>
```

80行目から109行目までのitemsオブジェクトの要素を消します。
そして以下のようにTodoリストのタスクのようなものを追加します。

```json
      items: [
            {
                id:1,
                task:'Vuetifyを勉強する',
                done: true,
            },
            {
                id:2,
                task:'JSを勉強する',
                done: false,
            },
      ],
```

この状態で保存してもまだ画面には出てきません。ロジックを書きましょう。

まずv-list-item-group内の要素31行目から63行目を削除しましょう。そしてv-list-item-groupのプロップスも消します。

v-listのtwo-lineを消します。すると以下の状態になっているはずです。

```vue
    <v-list>
      <v-list-item-group
      >
      </v-list-item-group>
    </v-list>
```

v-list v-forを追記しましょう。カラムを12に設定します。Vuetifyは特定のコンポーネントはcolsというapiをもっていてこれでカラムを指定できます。

```vue
    <v-list v-for="item in items" :key="item.id" cols="12">
```

v-list-item-group内にv-list-itemを書きましょう
v-list-item-content
v-list-item-title
それからitem.idをマスタッシュ構文で記載。

次にtodoのタスクを表示したいので、さきほどのようにv-list-item-contentから作り、マスタッシュ構文でitem.taskを記載します。

```vue
    <v-list v-for="item in items" :key="item.id" cols="12">
      <v-list-item-group
      >
        <v-list-item>
                    <v-list-item-content>
                        <v-list-item-title>{{item.id}}</v-list-item-title>
                    </v-list-item-content>
                    <v-list-item-content>
                        <v-list-item-title>{{item.task}}</v-list-item-title>
                    </v-list-item-content>
        </v-list-item>
      </v-list-item-group>
    </v-list>
```

そうするとようやくタスクが表示できます！![33fc7b7e28c8bf5913a79c60b0955c87.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/24dd123f-87fe-d7d6-a28f-38d6afbec934.png)

todoリストといえばチェックボックスですね！ということでチェックボックスとそのロジックをつくりましょう。

## 4.チェックボックスとクリックイベント

非常に簡単です。v-list-itemにv-list-item-aciton
v-checkboxを追加するだけです。

```vue

        <v-list-item>
            <v-list-item-action>
                <v-checkbox></v-checkbox>
              </v-list-item-action>

                    <v-list-item-content>
                        <v-list-item-title>{{item.id}}</v-list-item-title>
                    </v-list-item-content>
                    <v-list-item-content>
                        <v-list-item-title>{{item.task}}</v-list-item-title>
                    </v-list-item-content>
        </v-list-item>
```

![e646e8fe5d6a37bb6e8dcdbc72a1d0d4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/939632ae-6f82-f5e1-6d7c-0d1d60e1defd.png)

ただこれだけだと先程itemsオブジェクトに設定したdoneの値が反映されないのでコードを追加しましょう

v-checkboxにinput-valueを追加。

```vue
<v-checkbox  :input-value=item.done></v-checkbox>
```

これでitemsのdoneの値を読み込めるようになりました。今度はこれをクリックシた際にitemsに反映させるようにしましょう。

v-checkboxに@click="done(item.id)"を追加します

```vue
<v-checkbox  :input-value=item.done @click="done(item.id)"><v-checkbox>
```

scriptタグのdataの後に以下のメソッドを追加します。
dataの`selected: [2],`は消しちゃいましょう。

```js
    methods:{
        done(id){
            this.items[id].done = !this.items[id].done
            console.log(this.items[id].done)
        }
    }
```

このメソッドはチェックボックスがクリックされると現在bool型の状態とは逆の状態を設定します。
試しに保存してクリックしてみましょう。

![338d3377fb06e304524c671fbd775f92.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/be25fb79-8b76-1202-5eee-39f2d686e7b9.png)

何回か同じタスクのチェックボックスをクリックして開発者ツールのコンソールにtrue、falseがでればOKです！

次は不要なタスクを削除できるようにしましょう。

40行目以降に以下を追加してください。
削除するイベントを起こしたいのでv-list-item-actionを使い、その中にv-btnを書きます。今回はボタンにアイコンを使うのでプロップにiconを設定します。

```vue
<v-list-item-action>
 <v-btn @click="deleteTask(item.id)" icon>               
  <v-icon>
   mdi-backspace
  </v-icon></v-btn>
</v-list-item-action>
```
以下のようにアイコンが追加されます。これをクリックすると
@clickが走り、deleteTaskメソッドが呼び出されます。メソッドを書きましょう。

![be77cafd88f0b672786f22afbc721e6e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/03d33312-c237-f62f-8164-6eb3f88022a5.png)

単純にfilterでクリックされたタスク以外のidを持つものを返します。

```js
        deleteTask(id) {
        this.items = this.items.filter((item) => item.id !== id)
      },
```

では開発サーバーの方でボタンをクリックしてください。これで消えるはずです。

1点付け加えるとバブリングをさせたくないので@clickにstopをつけて@click.stopを使ったほうがベターです。

詳しくはこの方の記事を参照
https://qiita.com/shizen-shin/items/e25abd34219e1e569496

## タスクを追加する

最後にタスクを追加します。先程の削除と似ている処理なので簡単です。

以下をv-listの外に追加します

```vue
    </v-list>

          <v-col
          cols="12"
          class="d-flex">
          <v-list-item-content>
            <v-text-field
            v-model="todo"
            label="AddTodo"
            required
          ></v-text-field>
              </v-list-item-content>

              <v-list-item-action>
              <v-btn @click.stop="addTask(todo)" icon>               
                  <v-icon>
                    mdi-plus-circle
                  </v-icon>
                </v-btn>
              </v-list-item-action>
           </v-col>
```

すると以下のようになります。

![fd33847c19636c5edbc519609727a7d4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/6b4ae1f3-73ca-d25a-dc3d-f1778dfb5cf6.png)

リスト外にカラムを追加しその中のv-text-fieldに追加したいtodoを入力、プラスボタンをクリックしてtodoを追加という流れです。

メソッドを実装しましょう。
deleteTask(id)の下にaddTaskを追加。
itemsにpushして追加するというメソッドです。
ひとまず動くかidは3に固定して動かしてみましょう。

```js
addTask(todo){
        this.items .push({id:3,task:todo,done:false})
      },
```

このように追加でき、消すこともできますね！

![4cc2179625d6164d2f11a2320fda3009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/c9b9f789-6256-8c1e-1bab-8b9baaaae822.png)


idをランダムなものにしてみましょう。
Date関数を使います。

```js
     addTask(todo){
        let id = Date.now().toString()
        this.items .push({id:id,task:todo,done:false})
      },
```

とりあえず一意な値は振れましたね！

![822e1060d6778fa606f510a0cb801a75.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763385/ba17f265-64d0-d81b-a415-77d7cf3ec988.png)

お疲れ様でした。これにてこのチュートリアルは終了です。

# このチュートリアルを通して

まだまだ私も初心者ですが、プロジェクトで実際に使って感動したので、ひとまずVuetifyを使うと公式のコンポーネントを使って効率的に開発できるのを体感していただけたらと思い、共有したくてこのチュートリアルを作成しました。

今回使ったコンポーネントはまだまだVuetifyの一部でほかにも使い勝手のよいコンポーネントがたくさんあるのでこのチュートリアルをきっかけにぜひプロジェクトや個人制作などで使っていただけたらなと思います！