Nuxt3で簡単なToDoアプリを作っていく 〜リストレンダリング・イベントハンドリング〜

## 概要
Nuxt3がすごいらしいので、引き続きToDoアプリを作りながら学んでいきたいと思います。
イベントハンドリング・リストレンダリングを使って、動作の確認をしていきます。
内容はVue.js初心者向けです。

### 前回の記事
https://qiita.com/nishikor/items/c5e2e0682a4a7d522846

## 今回の記事のゴール
リストレンダリング、イベントハンドリングを使って入力内容を画面に表示できる。

## 完成コード
```app.vue
<template>
  <div>
    <h1>ToDoApp</h1>
    <h2>タスク追加</h2>
    <input v-model="taskName"><button @click="addTask()">追加</button>
    <h2>タスク名</h2>
    <div v-for="taskName in taskNameList" :key="taskName">
      {{ taskName }}<button @click="completeTask(taskName)">完了</button>
    </div>
  </div>
</template>

<script setup lang="ts">
const taskNameList = ref<string[]>(['Vue', 'Vue3', 'Nuxt3']);
const taskName = ref<string>('');
const addTask = () => {
  if (taskName.value === '') {
    return;
  }
  taskNameList.value.push(taskName.value);
  taskName.value = '';
}
const completeTask = (completedTaskName: string) => {
  taskNameList.value = taskNameList.value.filter((taskName) => completedTaskName !== taskName);
}
</script>
```
![完成形.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639493/75b4bd94-7bcf-ba35-003e-8b22202715de.gif)

## 書いていく
### ディレクティブについて
template内で`v-`という接頭辞を持ち、HTML要素の属性として定義できるものをディレクティブと呼びます。
Vueによって提供される特別な属性で、template内の要素にリアクティブな振る舞いを割り当てることができます。
前回のv-modelもディレクティブの一つで、input要素のvalue属性に対してリアクティブな振る舞いを提供しました。
他にも`v-for`ディレクティブや`v-if`ディレクティブなどが用意されています。

### リストレンダリング
`v-for`ディレクティブを用いることで、for文と同じように配列のデータを一件ずつ表示することができます。
`v-for="配列の中の一要素　in 配列"`といった形で表します。

まずはscriptの中から確認します。
表示確認を行うため、仮データを作成していきます。
変数名は`taskNameList`とし、リアクティブなデータを示すため`ref`を使用します。`ref`の隣にある`<>`は型名を表しているため、string型の配列を意味する`<string[]>`を記述しておきます。
```app.vue
<script setup lang="ts">
const taskNameList = ref<string[]>(['vue', 'Vue3', 'Nuxt3']);
</script>
```
仮データが準備できたので、次はtemplateです。
先ほど紹介した`v-for`ディレクティブを使用します。
`v-for="taskName in taskNameList"`とすることで配列内の'apple', 'banana', 'cherry'をそれぞれ出力することができます。

```app.vue
<template>
  <div>
    <h2>タスク名</h2>
    <div v-for="taskName in taskNameList" :key="taskName">
      {{ taskName }}
    </div>
  </div>
</template>
```

![スクリーンショット 2023-03-04 17.17.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639493/1f8daef6-b1e3-d25b-2f4e-95ce4c41fb9e.png)

#### 補足（keyについて）
div要素の中に`key`という属性が存在しているのがわかるかと思います。
これは、並び替えやデータの削除、追加などを行う際に、Vueがどの要素かを一意に特定するための重要な要素になります。`key`には文字列もしくは数値を指定します。

https://ja.vuejs.org/guide/essentials/list.html#maintaining-state-with-key

#### 補足（keyの前にある「:」について）
「:」は`v-bind`ディレクティブを表しています。頻繁に使用されるため、省略形が使用できるようになっています。
`v-bind`はその名の通り、属性とプロパティとを結びつける役割があります。
（今回はid属性に対して、taskNameプロパティを結びつけている）

### イベントハンドリング
v-onディレクティブを用いて、イベント発火時（クリックなど）にJavaScriptを実行することができます。
v-bind同様にこちらも省略表記が可能で、`@`で表記できます。
例えば`@click="addTask()"`だと、クリック時に`addTask`関数を実行するということになります。

### タスクの追加処理
タスクの追加処理を実装して、タスクのクリックイベントを実装していきます。

templateから見ていきます。
先ほどの`v-for`の上に、`v-model`ディレクティブを使用したinput要素と、追加ボタンを設置します。
追加ボタンはクリック時に、`addTask`関数を呼び出せるよう`@click="addTask()"`とします。
```app.vue
<template>
  <div>
    <input v-model="taskName">
    <button @click="addTask()">追加</button>
    <h2>タスク名</h2>
    <div v-for="taskName in taskNameList" :key="taskName">
      {{ taskName }}
    </div>
  </div>
</template>
```
次にscriptです。
`addTask`関数を新たに定義します。
`taskName`は`v-model`によって、input要素と繋がっているので、入力されているを常に保持しています。
そのため、クリック時に`taskName`の値は、input要素に入力されている値になります。
あとは、`taskNameList`配列に`push`をすることで加えることで、画面に入力した要素が追加されます。
あとは、「空入力時に配列にデータを入れない」「追加したら初期化する」処理をして、完了です。
```app.vue
<script setup lang="ts">
const taskNameList = ref<string[]>(['Vue', 'Vue3', 'Nuxt3']);
const taskName = ref<string>('');
const addTask = () => {
  if (taskName.value === '') {
    return;
  }
  taskNameList.value.push(taskName.value);
  taskName.value = '';
}
</script>
```
![名称未設定.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639493/7862e1ad-5689-4e51-3dfa-5f51d67a2e90.gif)

### タスクの削除処理
最後にタスクの削除処理を追加していきます。
同じくイベントハンドリングを使います。

templateからです。
追加ボタン同様、完了ボタンを追加します。
ただし、各タスクについて完了ボタンを使用したいので、`v-for`の中に用意します。
関数名は`completeTask`としています。
また、どのタスクが完了したのかを把握させるため、引数に`taskName`を入れておきます。
```app.vue
<template>
  <div>
    <input v-model="taskName">
    <button @click="addTask()">追加</button>
    <h2>タスク名</h2>
    <div v-for="taskName in taskNames" :key="taskName">
      {{ taskName }}
      <button @click="completeTask(taskName)">完了</button>
    </div>
  </div>
</template>
```

次にscriptです。
タスクの削除イコール配列からそのタスクを除けば良いので、以下の関数を用意します。

配列ようのfilterメソッドを使用して、完了したタスク名と同様のタスクを配列から削除する処理となっています。
```typescript
const completeTask = (completedTaskName: string) => {
  taskNames.value = taskNames.value.filter((taskName: string) => {
    return completedTaskName !== taskName
  });
}
```

あとは、タイトルをつければ完成です。
```app.vue
<template>
  <div>
    <h1>ToDoApp</h1>
    <h2>タスク追加</h2>
    <input v-model="taskName">
・・・省略
```

![完成形.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639493/75b4bd94-7bcf-ba35-003e-8b22202715de.gif)

## まとめ
画面上で動作する程度のものが完成しました。
次回はコンポーネントの分割を行っていきたいと思います。