Vue3 チュートリアル 2 Vueの基本的機能

今回はVueの基本的機能を触りながら、どういった事ができるのかを把握していきましょう。

### ゴール

- テンプレートのファイル構成を理解する
- Vueの基本的な機能を理解する
- Single File Componentでの実装を経験する
- Gitでローカルリポジトリにコミットする

https://qiita.com/TakahiRoyte/items/9033b61a0e528324da4c

## 準備

### ディレクトリの構成とコンポーネントの準備

前回作成したプロジェクトをVisual Studio Code(VSCode)で開きます。
VSCを開いて「フォルダーを開く」でも良いですし、
エクスプローラーから右クリックで「Codeで開く」でもOKです。
開いたらCtrl+Shift+Eか、画面左端のExplorerタブをクリックしてファイル構成を確認しましょう。

```bash:ファイル構成
|-- .vscode            プロジェクトで共有するVSCodeの設定ファイルが格納される
|-- node_modules       npmでインストールしたモジュールが格納される（基本いじらない）
|-- public             ソースコードで参照されないファイルを置く特別なディレクトリ
|-- src                Vueアプリのソースコード        
|   |-- assets         ソースコードから参照する静的ファイルを置く場所（画像やCSSなど）
|   |-- components     Vueのコンポーネント格納ディレクトリ
|   |-- App.vue        Vueアプリのトップレベルコンポーネント
|   `-- main.js        Vueアプリの起動場所
|-- .gitignore         Gitで管理しない無視するディレクトリ/ファイルを記載するファイル
|-- index.html         アプリケーションのエントリポイントであるHTMLファイル
|-- package-lock.json  npm install時のパッケージ情報（バージョン等）を保持するファイル
|-- package.json       npmコマンドや依存するnpmモジュールを管理するファイル
|-- README.md          読んでね
`-- vite.config.js     Vite用のコンフィグファイル
```

たくさんディレクトリなどがあるので混乱するかも知れませんが、
開発の時にソースコードを実装していくのは`src`ディレクトリで他はあまり触りません。
今回も`src`ディレクトリの中のファイルを編集・追加する形で実装していきます。

コンポーネントの変更による影響をすぐ確認できるようにするために、
`npm run dev`コマンドで開発環境を立ち上げ、[http://localhost:3000/](http://localhost:3000/)を開いておいてください。
Git BashのターミナルはVSCode上で起動するのがオススメです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/f0913078-9e96-f239-29ef-677def5d5c35.png)

今は、`App.vue`とcomponentsディレクトリ内の`HelloWorld.vue`が使われてますが、
`HelloWorld.vue`は削除し、`App.vue`は一旦まっさらにした状態で作成していきます。

まず`npm run dev`が実行されている状態のまま`HelloWorld.vue`ファイルを削除してください。
すると、アプリケーション側でもターミナル側でも同じエラーが表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/f6eee092-00dd-c02e-8b9b-baf8d9429983.png)

こういった形で、ViteはVueのコンポーネントの依存のエラーなどはメッセージで表示してくれます。
エラーが起きた時は落ち着いて文章を読んで対処してみてください。
今回は削除した`HelloWorld.vue`のインポートが解決しないというエラーなので、
`App.vue`側のインポートしている部分を削除することでエラーを解決します。
`App.vue`のコードを全て消して、以下のように修正して保存してみてください。

```vue:App.vue
<template>
  <h1>My Vue Playground</h1>
</template>
```

エラーが消えて、とてもシンプルな画面が表示されます。うまくいかない場合はリロードしてみてください。
Single File ComponentはHTML/CSS/JavaScriptの全てを含むと説明しましたが、
実はミニマルな状態ではHTMLを表す`<template>`だけでも実装可能です。
JavaScriptの`<script>`とCSSの`<style>`は必要に応じて実装します。


### Gitの準備

実際の開発では共有のリモートリポジトリ（GitLabやGitHub）からローカルリポジトリをクローンして利用します。
ですが、今回は自分でローカルリポジトリを作ってコミットしていく流れも合わせて体験していきましょう。
まずは`git init`コマンドで、ローカルリポジトリを作成します。

```bash
$ git init
Initialized empty Git repository in C:/dev/vite-vue-project/.git/
```

そうすると隠しフォルダの`/.git`ディレクトリが作成されます（VSCode上からはデフォルトで見えません）。
同時にVSCodeのExplorerエリアのファイルの大多数が以下のような表示になり、
Source Controlに数字付きのバッジが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/1b02c181-01f2-d14e-8379-5ab9f3ac3a34.png)

緑で「U」がついているファイルはUntracked FileといってGitで管理されていないファイルやディレクトリを示します。
`node_modules`ディレクトリがグレーなのは、`.gitignore`内で`node_modules`を無視するよう定義しているためです。
`.gitignore`内のファイルはGitによる管理の対象外になります。

```text:.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

（以下略）
```

左のSource Controlボタンを押すと、ExplorerがSource Controlに切り替わります。
ここでは、変更があったファイルが全て差分とともに表示されます。
今回は初期コミットなので、全てのファイルが新規作成(Untracked)状態です。
この状態で、Changes横のStage All Changesのプラスマークを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/87071864-f1a5-6c87-6cd7-776e24474879.png)

すると、ファイル横の「U」が「A」に変わります。これはAddedのAで、ステージングされたことを示します。
このステージングの作業は、今のようにまとめて行うこともできれば、ファイル一個単位でも可能で、
その場合はファイル名のすぐよこにカーソルを合わせると表示されるプラスマークを押します。
コミットしたいファイルがすべてステージされていることを確認したら、
Message欄に任意のメッセージを入力して、チェックマークを押してコミットしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/2c3b90df-6f33-7b0f-6aed-263ca879f55c.png)

これで今の状態がローカルリポジトリにコミットされました。
試しにApp.vueの文字に変更を加え保存してみると、Modifiedの「M」の文字がでます。
リモートブランチにプッシュする際は今の一連の流れを新たなブランチで実施して、
マージリクエストを投げるようにしてください。

### 保存時のフォーマットの有効化

Ctrl+Shift+pで出てきたフィールドにWorkspaceと打ち込みWorkspace Settingsを表示してください。
するとワークスペースの設定画面がでるので、formatと入力しFormat On Saveのチェックをオンにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/5fb3d86c-b575-44f4-ea73-e3a558029848.png)

そうするとディレクトリ内の`.vscode/settings.json`というファイルが生成されます。
これはワークスペース単位の設定なので共同開発時には他の開発者と共有すべきファイルです。
ですが、このテンプレートだと`.gitignore`の対象になっているので、
`.gitignore`に`!.vscode/settings.json`を追記してください。
このように`.gitignore`は先頭に`!`をつけることで特定のファイルを無視する対象から除外可能です。
`extensions.json`が`.vscode/*`配下でもあるに関わらずコミットされているのはそのためです。

## Vueの基本機能

### 宣言的レンダリング Declarative Rendering

ここからは`App.vue`でいろいろな実装を試していきます。
まずは、JavaScriptのデータをTemplate上で表示する宣言的レンダリングです。
`{{  }}`を使って、Vueのコンポーネントの`data`の値を表示します。
まずは、ヘッダータグに任意のデータを表示してみましょう。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title'
    }
  }
}
</script>

<template>
  <h1>{{ title }}</h1>
</template>
```

これで、画面上のタイトルが`<script>`の`data`内に記述した値に変更されました。
VueのOptions APIでは`export default`でエクスポートしているオブジェクトの、
様々なオプションにデータやメソッドなどを実装していき、それをテンプレートと紐づけ動的なアプリを実現します。
この`data`内の値はリアクティブであり、値が更新されると表示も同時に更新されます。

### v-bind 属性バインディング

HTML内のテキストだけでなく、要素の属性とも文字列をバインディング（結びつける）ことが可能です。
`data`に`message`を追加して、テンプレート側で`h1`要素に`title`属性を追加してみましょう。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue'
    }
  }
}
</script>

<template>
  <h1 v-bind:title="message">{{ title }}</h1>
</template>
```

これを保存すると画面上で変化は見えませんが、
ヘッダーにマウスオーバーすると`message`の値が表示されるはずです。
このv-bindは他にもあらゆる属性と結びつけが可能です。
よく使う`class`とのバインディングを試してみましょう。

通常クラスを指定する時は`class="red"`のように実装します。
Vueで動的にクラスの割り当てを管理する時は、オブジェクトを用意し、
クラス名に対しbooleanの値でクラスを当てるか当てないかの切り替えが可能です。
ヘッダータグの色を変えるためにクラスを当てる以下のコードを試してみてください。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: "My New Vue Title",
      message: "Welcome to Vue",
      isRed: true,
    };
  },
};
</script>

<template>
  <h1 v-bind:title="message" v-bind:class="{ red: isRed }">{{ title }}</h1>
</template>

<style>
.red {
  color: red;
}
</style>
```

スクリプト内の`isRed`が`true`なので、テンプレート内の`:class`にて`red`クラスがあたり、
新たに追加した`<style>`内のクラスが適用され文字色が赤くなります。
試しに`isRed`を`false`にしてみてどういう変化をするか確認してみてください。

このオブジェクトはテンプレート側で定義してもOKですし、スクリプト側で定義するでもOKです。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      textStatus: {
        red: true,
      },
    };
  },
};
</script>

<template>
  <h1 v-bind:title="message" v-bind:class="textStatus">{{ title }}</h1>
</template>

<style>
.red {
  color: red;
}
</style>
```

状況によって使い分ける必要があります。
適用するクラスが細かく分かれている場合はスクリプト、単体ならテンプレートにするのがオススメです。
あるいは、すべてどちらかに統一するでも良いと思います。その場合は責務の関係上テンプレート側がオススメです。
今回は一旦テンプレート側で記載する方針で進めます。`isRed`を使っていたコードに戻してください。

v-bindはVueで開発する際に多用します。そのため略記法が用意されているので、使ってみましょう。
`v-bind:xxx`の`v-bind`を削除して`:xxx`と先頭にコロンが付く形でも`v-bind`とみなされます。
テンプレートを以下のように修正してください。

```vue
<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
</template>
```

同様に機能していることを確認してみてください。
このような`v-`から始まるVueが用意する属性をディレクティブと言います。
このディレクティブや`{{ }}`で指定する値は単一のJavaScriptの式としてみなされます。
なので直接式を書き込むような以下のようなコードでも動作します。

```vue
<template>
  <h1 :title="123 * 3" :class="{ red: !true }">{{ 'ようこそ' + 'Vueへ' }}</h1>
</template>
```

動作を確認したらコードを戻しておいてください。

### computed 算出プロパティ

テンプレート内に式を書けることを紹介しましたが、
多くの場合テンプレート内の記述が冗長になり肥大化しメンテナンス性が下がります。
また、同じ値を何回も利用したい場合、同じ式をリピートして書かなくてはいけません。
以下で言えば、フルネームを表示するために同じ式を2度書いています。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      user: {
        firstName: 'John',
        lastName: 'Smith',
      },
    };
  },
};
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <h2>{{ user.firstName + " " + user.lastName }}さんのデータ</h2>
  <p>Name: {{ user.firstName + " " + user.lastName }}</p>
</template>
```

そういった場合のためにVueでは`computed`オプション（算出プロパティ）が用意されています。
`computed`を利用してフルネームを返すプロパティを作成してみましょう。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      user: {
        firstName: 'John',
        lastName: 'Smith',
      },
    }
  },
  computed: {
    fullName() {
      return this.user.firstName + ' ' + this.user.lastName
    },
  },
}
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <h2>{{ fullName }}さんのデータ</h2>
  <p>Name: {{ fullName }}</p>
</template>
```

`computed`はdataの値に対してリアクティブに更新されます。
`fullName`で`user`オブジェクトを利用する際に、先頭に`this.`がついていることを留意してください。
これは、Vueインスタンス内で利用する`data`,`computed`,`methods`などのオプションの各プロパティは、
`this.`を先頭に書いてこのVueインスタンスのプロパティであることを指定しなくてはいけません。
テンプレート側では`this.`は不要なので、混乱しないように注意してください。

後に紹介するメソッドを指定することで似たことは実現できますが、
computedの値はキャッシュがなされパフォーマンスの最適化がなされます。
可能な限りcomputedを使うようにしましょう。

### v-if & v-else 条件付きレンダリング

テンプレート内のブロックを条件に応じて描画したい時に`v-if`ディレクティブを利用します。
`v-if`はスクリプト内のbooleanな値に対してリアクティブに描画が切り替わります。
ユーザーデータにメンバーであるかの`isMember`をもたせ、それに応じて表示を切り替えてみます。

```vue:App.data
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      user: {
        firstName: 'John',
        lastName: 'Smith',
        isMember: true,
      },
    }
  },
  computed: {
    fullName() {
      return this.user.firstName + ' ' + this.user.lastName
    },
  },
}
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <h2>{{ fullName }}さんのデータ</h2>
  <p>Name: {{ fullName }}</p>
  <p v-if="user.isMember">メンバーです</p>
</template>
```

`isMember`が`true`の場合、テンプレートの`<p v-if="user.isMember">メンバーです</p>`が表示されます。
`isMember`を`false`に変えて表示が消えることを確かめてみてください。
続いて、`v-if`とセットで使う`v-if`に該当しない場合に表示するための`v-else`ディレクティブを利用します。

```vue
<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <h2>{{ fullName }}さんのデータ</h2>
  <p>Name: {{ fullName }}</p>
  <p v-if="user.isMember">メンバーです</p>
  <p v-else>メンバーではありません</p>
</template>
```

`v-else`ディレクティブを追加した状態で`isMember`の`true/false`を切り替えて表示の確認をしてください。
`v-if`及び`v-else`は指定した子要素まで影響が及びます。

### v-for リストレンダリング

リストのデータを元に同じ要素を繰り返し描画したいケースは多くあると思います。
たとえば、複数のユーザーや複数の日付のデータを表示するなどのケースです。
そういったケースでは、`v-for`ディレクティブを利用します。
今回はユーザーデータをリスト（配列）にして複数ユーザーの表示を試します。
大きくコードを変えるので注意してください。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      users: [
        {
          firstName: 'John',
          lastName: 'Smith',
          isMember: true,
        },
        {
          firstName: 'Taro',
          lastName: 'Shinjuku',
          isMember: false,
        },
        {
          firstName: 'Hanako',
          lastName: 'Shibuya',
          isMember: true,
        },
      ],
    }
  },
}
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <h2>ユーザーのデータ</h2>
  <div v-for="user in users">
    <p>Name: {{ user.firstName + ' ' + user.lastName }}</p>
    <p v-if="user.isMember">メンバーです</p>
    <p v-else>メンバーではありません</p>
  </div>
</template>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/155e7c8a-678b-6f31-9d41-a38460e3e775.png)

`data`の`user`を`users`と名前を変えて配列データに変え、
それをテンプレート内で`div`要素に`v-for`ディレクティブを追加し繰り返し描画しています。
`v-for="user in user"`の`users`が`data`の`users`を指定しており、
その配列の要素1つ1つを`user`として`v-for`内では利用可能になっています。
`v-for`は入れ子になって複数の階層でも利用可能です。

### v-on & methods イベントハンドリング

ここまではコンポーネントの値を元にテンプレート側で表示をしてきました。
ここからは、テンプレート側で発生したイベントを元に特定の処理を実施するための実装を試します。
`v-on`ディレクティブでDOMイベントを拾い、イベント発火時にJavaScriptのコードを実行できます。
実行するコードはシンプルなものならテンプレート内に書くこともできますが、
基本的にはOptions APIの`methods`プロパティにメソッドとして定義します。
今回はデータは固定ですがユーザーをリストに追加する処理とそれを実行するボタンを作ります。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      users: [
        {
          firstName: 'John',
          lastName: 'Smith',
          isMember: true,
        },
        {
          firstName: 'Taro',
          lastName: 'Shinjuku',
          isMember: false,
        },
        {
          firstName: 'Hanako',
          lastName: 'Shibuya',
          isMember: true,
        },
      ],
    }
  },
  methods: {
    addUser() {
      this.users.push({
        firstName: 'First',
        lastName: 'Last',
        isMember: true,
      })
    },
  },
}
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <button v-on:click="addUser">ユーザー追加</button>
  <h2>ユーザーのデータ</h2>
  <div v-for="user in users">
    <p>Name: {{ user.firstName + ' ' + user.lastName }}</p>
    <p v-if="user.isMember">メンバーです</p>
    <p v-else>メンバーではありません</p>
  </div>
</template>
```

`button`要素に`v-on:click="addUser"`というディレクティブが実装されています。
これはボタン要素のクリックイベントを拾うと`addUser`を実行するという定義になってます。
`addUser`はスクリプト内の`methods`に定義された関数で、
`data`に定義されている`users`に固定のユーザーデータを`push`で追加しています。

`v-on`ディレクティブはかなり多用するので、これも略記方が用意されています。
`v-on:click`を`@click`と書くことが可能です。基本的にこちらを利用しましょう。
以下のように修正して同じように動くか試してみてください。

```vue
  <button @click="addUser">ユーザー追加</button>
```

`v-on`は`click`イベントだけでなく`blur`や`keydown`など基本的なイベントが利用可能です。
`onclick`から`@click`、`onfocus`から`@focus`のようにすることで利用したいイベントに応じて、
ハンドラーを設定して実装をしていきましょう。

### v-model フォームバインディング

フォームの値をVueのアプリケーションでハンドリングしたいケースは多々あります。
これまで試してきた`v-bind`や`v-on`でそれらを実装することも可能ですが、
Vueでは`v-model`ディレクティブが用意されておりこれにより簡単にフォームとデータのバインディングが可能です。
先程は固定の値を追加していたユーザー追加ボタンですが、これを任意の値を入力できるようにしましょう。

```vue:App.vue
<script>
export default {
  data() {
    return {
      title: 'My New Vue Title',
      message: 'Welcome to Vue',
      isRed: true,
      input: {
        firstName: '',
        lastName: '',
        isMember: true,
      },
      users: [
        {
          firstName: 'John',
          lastName: 'Smith',
          isMember: true,
        },
        {
          firstName: 'Taro',
          lastName: 'Shinjuku',
          isMember: false,
        },
        {
          firstName: 'Hanako',
          lastName: 'Shibuya',
          isMember: true,
        },
      ],
    }
  },
  methods: {
    addUser() {
      this.users.push(this.input)
      this.input = {
        firstName: '',
        lastName: '',
        isMember: true,
      }
    },
  },
}
</script>

<template>
  <h1 :title="message" :class="{ red: isRed }">{{ title }}</h1>
  <input type="text" v-model="input.firstName" />
  <input type="text" v-model="input.lastName" />
  <input type="checkbox" v-model="input.isMember" />
  <button v-on:click="addUser">ユーザー追加</button>
  <h2>ユーザーのデータ</h2>
  <div v-for="user in users">
    <p>Name: {{ user.firstName + ' ' + user.lastName }}</p>
    <p v-if="user.isMember">メンバーです</p>
    <p v-else>メンバーではありません</p>
  </div>
</template>
```

これで入力した値に紐づく形でユーザー情報が追加できるようになりました。
`addUser`メソッド内では`users`にプッシュする値を`input`にし、
それを追加後`input`の内容をリセットすることで同じデータを追加しないようにしています。
現状では名前が空でも追加できるので、もし余裕がある方はバリデーション機能を実装してみてください。

今回は`input`タグの`text`と`checkbox`を利用していますが、異なるタイプにも対応しています。
他の使い方については[公式ドキュメント](https://vuejs.org/guide/essentials/forms.html)を確認してください。

### Gitのコミット

さいごに修正したApp.vueをGitのローカルリポジトリにコミットして終了です。

## まとめ

今回はVueの以下の基本的な機能を実装しながら学習しました。

- `{{ }}`テンプレート構文
- `computed`算出プロパティ
- `v-bind`によるバインディング
- `v-if`と`v-else`による条件付きレンダリング
- `v-for`によるリストレンダリング
- `v-on`と`methods`イベントハンドリング
- `v-model`によるフォームバインディング

以上の基本的な機能をマスターすることで、
Vueでは動的なアプリの実装が可能になります。
次回のチュートリアルではここで学んだ内容をベースにToDoアプリを作成していきます。

https://qiita.com/TakahiRoyte/private/a9aac2f069b99f3378c7