Vue.jsのチュートリアルを触ってみる #入門

## はじめに

本記事では、JavaScriptフレームワークの一つであるVue.jsの公式チュートリアルを実行していく中で学んだことを記事にまとめていきたいと思います。

前置きが少しあるのでチュートリアルのコードから見ていきたい方は[コチラ](#APIスタイル)から読み進めてください！

最初に、参考にした公式サイトを載せておきます。

https://ja.vuejs.org/tutorial/#step-1

## 実行方法について

公式のクイックスタートページにてチュートリアル環境以外に３つの実行環境が提供されていましたので紹介します。

### Playground

https://play.vuejs.org/#eNo9jcEKwjAMhl/lt5fpQYfXUQfefAMvvRQbddC1pUuHUPrudg4HIcmXjyRZXEM4zYlEJ+T0iEPgXjn6BB8Zhp46WUZWDjCa9f6w9kAkTtH9CRinV4fmRtZ63H20Ztesqiylphqy3R5UYBqD1UyVAPk+9zkvV1CKbCv9poMLiTEfR2/IXpSoXomqZLtti/IFwVtA9A==

### JSFiddle

https://jsfiddle.net/yyx990803/2ke1ab0z/

### StackBlitz

https://vite.new/vue

２、３番目の環境での実行方法がよくわからなかったので操作なしで変更内容が反映される１番のPlaygroundが初学者にはお勧めです。私も１番で学習を進めていきます。

もしローカルマシン上で動かしたい方がいらっしゃればサイトの下部に説明があったので参考にしてください！

## Vue.jsの主な機能

これから出てくる言葉は、まだ全て理解できなくても良いと公式から温かい言葉をいただいたので補足程度に調べたことを付け加えようと思います

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

Vue では、標準的な HTML を拡張したテンプレート構文を使って、HTML の出力を宣言的に記述することができます。この出力は、JavaScript の状態に基づきます。

> テンプレート構文を使用して、データをHTMLに結びつけることを意味します。テンプレート内でデータバインディングを行うことで、複雑なアプリケーションでもコードがシンプルになります。

> レンダリング：
デジタル情報を視覚的に表示するプロセスを指します。これは、Webページやアプリケーションにおいて、データやコードを視覚的に表示するために不可欠なプロセスです。

### リアクティビティー

Vue は JavaScript の状態の変化を自動的に追跡し、変化が起きると効率的に DOM を更新します。

> この機能により、開発者は状態管理やDOM操作のコードを書く必要が減り、アプリケーションの開発が簡素化されるそうです。

## シングルファイルコンポーネント(SFC)とは

SFCとは、HTML、JavaScript、CSSを一つのファイルにまとめて記述できる形式です。これにより、コンポーネントごとに自己完結したコードを管理しやすくなります。

SFCは3つの主要なセクションで構成されています。

- `<template>`セクション

ここではコンポーネントのHTMLテンプレートを定義します。

- `<script>`セクション

ここでは、コンポーネントのロジック（データ、メソッド、ライフサイクルフックなど）を記述します。

- `<style>`セクション

ここでは、コンポーネントのスタイルを定義します。スコープ付きスタイルを適用することもできます。

## APIスタイル

Vue コンポーネントを作成する際は、`Options API`、そして`Composition API`と呼ばれる 2 種類の異なる API スタイルが利用できます。

これから順に二つの特徴を説明していきます。

### Options API

Options API では、data、methods、mounted といった数々のオプションからなる 1 つのオブジェクトを用いてコンポーネントのロジックを定義します。

定義されたプロパティには、コンポーネントのインスタンスを指す this を使って、関数内からアクセスできます。

```vue:Options API使用例
<script>
export default {
  // data() で返すプロパティはリアクティブな状態になり、
  // `this` 経由でアクセスすることができます。
  data() {
    return {
      count: 0
    }
  },

  // メソッドの中身は、状態を変化させ、更新をトリガーさせる関数です。
  // 各メソッドは、テンプレート内のイベントハンドラーにバインドすることができます。
  methods: {
    increment() {
      this.count++
    }
  },

  // ライフサイクルフックは、コンポーネントのライフサイクルの
  // 特定のステージで呼び出されます。
  // 以下の関数は、コンポーネントが「マウント」されたときに呼び出されます。
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
```

### Composition API

Composition APIでは、インポートした各種 API 関数を使ってコンポーネントのロジックを定義していきます。

SFC において、Composition API は通常、`<script setup>` と組み合わせて使用します。
このタグになっていることがComposition APIを使用していることを示します。

```vuejs:Composition API使用例
<script setup>
import { ref, onMounted } from 'vue'

// リアクティブな状態
const count = ref(0)

// 状態を変更し、更新をトリガーする関数。
function increment() {
  count.value++
}

// ライフサイクルフック
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
```

## 宣言的レンダリング

宣言的レンダリング（declarative rendering）とは、アプリケーションのUIを宣言的に記述することで、データとビュー（UI）の関係をシンプルに表現する方法です。

JavaScript の状態に基づいて HTML がどのように見えるかを記述することができます。

早速ソースコードを見ていきます

```vuejs:Hello World(Option API)
<script>
export default {
  data() {
    return {
      message: 'Hello',
    }
  }
}
</script>

<template>
  <h1>{{ message + ' World!' }}</h1>
</template>
```

`message`プロパティはテンプレート内で使用可能です。このように、[mustaches構文][mustaches構文]を使い、 message の値に基づいた動的なテキストをレンダリングすることができます。

## 属性バインディング

Vueでは、動的な値を属性にバインドするのに、`v-bind`ディレクティブを使います。

ディレクティブは`v-`から始まる特別な属性であり、テンプレート構文内で使用されます。

特に`v-bind`は頻繁に使用されるため省略記法が存在します。

```vuejs:v-bind使用例
<script>
export default {
  data() {
    return {
      titleClass: 'title'
    }
  }
}
</script>

<template>
　<!-- ココ -->
  <h1 v-bind:class="titleClass">Make me red</h1>
  <!-- 省略すると -->
  <h1 :class="titleClass">Make me red</h1>
</template>

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

これを実行すると２行赤い文字で「Hello World!」と出力されます。

`v-bind`ディレクティブは、要素のclassという属性を、コンポーネントが持つtitleClassというプロパティと同期させるよう Vue に指示しています。

## イベントリスナー

`v-on`ディレクティブを使うことでDOMイベントを登録時やイベント発火時にいくつかの JavaScriptを実行することができます。

こちらも`@`に省略することができます。使い方は`v-on:click="handler"`、あるいは省略して`@click="handler"`として使用します。

```vuejs:v-on使用例
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
```

メソッドの中では、this を使ってコンポーネントインスタンスにアクセスしています。

## フォームバインディング

ここではまず
「`v-bind` ＋ `v-on` ＝ `v-model`」
を覚えてください。

両辺とも意味は等しく、input要素に双方向（two-way）バインディングを作成することができます。

難しく聞こえますが、入力されたものをテキストとして出力するときに使います。

```vuejs:v-model使用例
<script>
export default {
  data() {
    return {
      text: ''
    }
  },
  methods: {
    onInput(e) {
      this.text = e.target.value
    }
  }
}
</script>

<template>
  <!-- ココ -->
  <input :value="text" @input="onInput" placeholder="Type here">
  <!-- ココ：どちらも意味は一緒で下の場合メソッドはいらない -->
  <input v-model="text" placeholder="Type here">
  <p>{{ text }}</p>
</template>
```

覚える量は増えますが、よく使われているので覚えてしまった方がコードも短く書くことが出来るのでお勧めだと思います。

## 条件付きレンダリング

`v-if`ディレクティブは、ブロックを条件に応じてレンダリングしたい場合に使用されます。ブロックは、ディレクティブの式が真を返す場合のみレンダリングされます。

`v-if`に対して "else block" を示すために、`v-else`ディレクティブを使用できます

`v-else-if`は、名前が示唆するように、`v-if` の "else if block" として機能します。

```vuejs:v-if使用例
<script>
export default {
  data() {
    return {
      awesome: true
    }
  },
  methods: {
    toggle() {
      //論理値の反転
      this.awesome = !this.awesome
    }
  }
}
</script>

<template>
  <button @click="toggle">Toggle</button>
  <h1 v-if="awesome">Vue is awesome!</h1>
  <h1 v-else>Oh no 😢</h1>
</template>
```

ここではトグルボタン機能の実現のために`v-if`のみを使用しています。

## リストレンダリング

配列に基づいて項目のリストをレンダリングするには、`v-for` ディレクティブを使用します。

`v-for`ディレクティブでは、`item in items`という形式の特別な構文が必要になります。ここで、`items`は元のデータの配列を指し、`item`は反復処理の対象となっている配列要素のエイリアスを指します

> JavaScriptでいう`for Each`文に該当します。

では、リスト一覧を表示するサンプルコードを見てみます。

```vuejs:v-for使用例
<script>
// give each todo a unique id
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      <!--リスト-->
      todos: [
        { id: id++, text: 'Learn HTML' },
        { id: id++, text: 'Learn JavaScript' },
        { id: id++, text: 'Learn Vue' }
      ]
    }
  },
  methods: {
    addTodo() {
      <!--追加ボタンのメソッド-->
      this.todos.push({ id: id++, text: this.newTodo })
      this.newTodo = ''
    },
    removeTodo(todo) {
      <!--削除ボタンのメソッド-->
      this.todos = this.todos.filter((t) => t !== todo)
    }
  }
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <!--入力フォーム-->
    <input v-model="newTodo" required placeholder="new todo">
    <button>Add Todo</button>
  </form>
  <!--リスト一覧の表示-->
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>
```

- required
HTMLの標準属性です。この属性が付いていると、フォームの送信時にこの入力フィールドが空白であってはならないことを示します。ユーザーが入力を行わなければ、フォームは送信されず、ブラウザが警告を表示します。

> v-for と v-if を組み合わせ、同じノードに両方が存在する場合、v-for よりも v-if のほうが優先順位が高くなることに注意してください。

## 算出プロパティー

他のプロパティーからリアクティブに算出されたプロパティーを computed オプションを使用して宣言することができます。

状態に基づいて異なるリスト項目をレンダリングするために用います。

```vuejs:算出プロパティの使用例
<script>
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
  computed: {
    // 算出プロパティ実装部分 
    filteredTodos() {
      return this.hideCompleted
      //論理値を逆にする
        ? this.todos.filter((t) => !t.done)
        : this.todos
    }
  },
  methods: {
    addTodo() {
      this.todos.push({ id: id++, text: this.newTodo, done: false })
      this.newTodo = ''
    },
    removeTodo(todo) {
      this.todos = this.todos.filter((t) => t !== todo)
    }
  }
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" required placeholder="new todo">
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>
</template>

<style>
.done {
  text-decoration: line-through;
}
</style>
```

> メソッドとして実現しても良いのですが、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるため依存関係が更新されたときにだけ再評価されます。

正直ここのパートは私もよく理解できなかったので[公式サイト][算出]を参照することをお勧めします。

## ライフサイクルとテンプレート参照

これまで紹介してきたソースコードでは、リアクティビティーと宣言的レンダリングのおかげですべての DOM 更新を処理してくれました。しかし、DOMを手動で操作する必要のある場合があります。

コンポーネントがマウントされた後に`ref`属性を用いてテンプレート内の特定の要素やコンポーネントに直接アクセスすることができます。

> ref は、要素や子コンポーネントへの参照を登録するために使用します。

```vuejs:テンプレートの参照
<script>
export default {
  mounted() {
    this.$refs.pElementRef.textContent = 'mounted'
  }
}
</script>

<template>
  <p ref="pElementRef">Hello</p>
</template>
```

ソースコードでは、コンポーネントがDOMにマウントされると（mounted フックが呼ばれる）、mounted() メソッドが実行されテキストコンテンツを書き換えています。

> 各 Vue コンポーネントインスタンスは、生成時に一連の初期化を行います - 例えば、データ監視のセットアップ、テンプレートのコンパイル、インスタンスの DOM へのマウント、データ変更時の DOM の更新が必要になります。その過程で、ライフサイクルフックと呼ばれる関数も実行され、ユーザーは特定の段階で独自のコードを追加することが可能です。

## ウォッチャー

ウォッチャーを用いることで数値が変化したらコンソールにログを記録するなど、 プロパティの変更を監視することが出来ます。

ソースコードを見てみます。

```vuejs:ウォッチャー使用例
<script>
export default {
  data() {
    return {
      todoId: 1,        // 取得するToDoのID
      todoData: null    // 取得したToDoデータを格納する変数
    }
  },
  methods: {
    async fetchData() {
      this.todoData = null;  // データ取得前にtodoDataを初期化
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${this.todoId}`
      );  // JSONPlaceholderからToDoデータを取得
      this.todoData = await res.json();  // 取得したデータをtodoDataに格納
    }
  },
  mounted() {
    this.fetchData();  // コンポーネントがマウントされたときにデータを取得
  },
  watch: {
    todoId() {
      this.fetchData();  // todoIdが変更されたらデータを再取得
    }
  }
}
</script>
<template>
  <p>Todo id: {{ todoId }}</p>  <!-- 現在のToDoのIDを表示 -->
  <button @click="todoId++" :disabled="!todoData">Fetch next todo</button>  <!-- ToDoのIDをインクリメントするボタン -->
  <p v-if="!todoData">Loading...</p>  <!-- データ取得中のローディングメッセージ -->
  <pre v-else>{{ todoData }}</pre>   <!-- 取得したToDoデータを表示 -->
</template>
```

`watch`オプションでは、todoId の変更を監視します。
todoId が変更されると、fetchData メソッドを呼び出して、新しい todoId に対応するToDoデータを取得します。

## コンポーネントの利用方法

親コンポーネントは、そのテンプレートにある別のコンポーネントを子コンポーネントとしてレンダリングすることができます。

子コンポーネントを使用するには、まずそれをインポートする必要があります。次に、components オプションを使用して、コンポーネントを登録します。

ではソースコードを見てみましょう

```vuejs:ChildComp.vue
<template>
  <h2>A Child Component!</h2>
</template>
```

```vuejs:App.vue
<script>
import ChildComp from './ChildComp.vue'

export default {
  components: {
    ChildComp
  }
}
</script>

<template>
  <ChildComp />
</template>
```

ここでは、オブジェクトプロパティのショートハンドを使って、 ChildComp コンポーネントを ChildComp キーの下に登録しています。

## props

子コンポーネントは、親コンポーネントから props を介して入力を受け取ることができます。そして親は属性と同じように、props を子に渡すことができます。動的な値を渡すには、v-bind という構文も使えます。

```vuejs:ChildComp.vue
<script>
export default {
  props: {
    msg: String  // 親コンポーネントから受け取るプロパティ 'msg' の型を String として定義
  }
}
</script>
<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>
```

```vuejs:親コンポーネント
<script>
import ChildComp from './ChildComp.vue'

export default {
  components: {
    ChildComp
  },
  data() {
    return {
      greeting: 'Hello from parent'
    }
  }
}
</script>

<template>
  <ChildComp :msg="greeting" />
</template>
```

親コンポーネントの data オブジェクトには greeting というプロパティがあり、これが子コンポーネントの msg プロパティに渡されます。

## イベントの発行

子コンポーネントはprops を受け取るだけでなく、親コンポーネントにイベントを発行することが出来ます。

```vuejs:ChildComp.vue
<script>
export default {
  emits: ['response'],  // 'response' イベントを親コンポーネントに送信することを宣言
  created() {
    this.$emit('response', 'hello from child');  // 'response' イベントを 'hello from child' メッセージとともに送信
  }
}
</script>
<template>
  <h2>Child component</h2>
</template>
```
```vuejs:親コンポーネント
<script>
import ChildComp from './ChildComp.vue'

export default {
  components: {
    ChildComp
  },
  data() {
    return {
      childMsg: 'No child msg yet'
    }
  }
}
</script>

<template>
  <!-- childMsgの書き換え -->
  <ChildComp @response="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>
```
## スロット

props を経由したデータの受け渡しだけでなく、親コンポーネントはテンプレートフラグメントを __スロット__ を経由して子コンポーネントへ渡すこともできます。

`slot`要素を利用して親コンポーネントからのスロットコンテンツをレンダリングします。

> こちらも書いたはいいものの理解できていないので詳しく知りたい方は[コチラ][surotto]を参考にしてください。

ソースコードを見てみます

```vuejs:ChildComp.vue
<template>
  <slot>Fallback content</slot>
</template>
```

```vuejs:親コンポーネント
<script>
import ChildComp from './ChildComp.vue' // 子コンポーネントをインポート

export default {
  components: {
    ChildComp  // 子コンポーネントを登録
  },
  data() {
    return {
      msg: 'from parent'  // 親コンポーネントのメッセージデータ
    }
  }
}
</script>

<template>
  <ChildComp>Message: {{ msg }}</ChildComp>
</template>
```

処理の流れを説明していきます。

１．親コンポーネントが初期化され、データオブジェクト msg に 'from parent' がセットされます

２．親コンポーネントのテンプレート内で <ChildComp>Message: {{ msg }}</ChildComp> と記述することで、子コンポーネントのスロットに Message: from parent というコンテンツが渡されます。

３．子コンポーネント ChildComp はスロットを使って親コンポーネントから渡されたコンテンツ Message: from parent を表示します。

## さいごに

本記事では、Vue.jsのチュートリアルを用いて学んだことを記述しました。今回の学習内容のみでは自力ではソースコードを記述できなくても、他人が書いた簡単なソースコードの意味をギリ解釈できるかもというのが正直な感想です。

チュートリアルに関する内容はすべて書いたつもりですが、これからは[メインガイド][メインガイド]を用いて学習したより詳しいVueの内容を投稿できればなと思います！

[mustaches構文]: https://ja.vuejs.org/guide/essentials/template-syntax
[算出]: https://ja.vuejs.org/guide/essentials/computed
[メインガイド]: https://ja.vuejs.org/guide/essentials/application
[surotto]: https://ja.vuejs.org/guide/components/slots.html