Vue + <script setup> + TypeScript: Vue 3と新構文で変わったところをチュートリアルから拾い出してみる

Vue 3が広まるとともに、構文も[Options API](https://v3.ja.vuejs.org/api/options-api.html)ではなく、[Composition API](https://v3.ja.vuejs.org/api/composition-api.html)が用いられるようになってきました。TypeScriptとの相性も高まっています(「[Composition API とともに TypeScript を使用する](https://ja.vuejs.org/guide/typescript/composition-api.html)」参照)。さらに加わったのが、単一ファイルコンポーネント(SFC)の`<script setup>`構文です。

> `<script setup>`は単一ファイルコンポーネント(SFC)内で [Composition API](https://v3.ja.vuejs.org/api/composition-api.html)を使用するコンパイル時のシンタックスシュガー(糖衣構文)です。SFCとComposition APIの両方を使うならば、おすすめの構文です。
(「[SFC`<script setup>`](https://v3.ja.vuejs.org/api/sfc-script-setup.html)」)

そこで少し前に書いたのが、新構文で公式サイト作例の[TodoMVC](https://ja.vuejs.org/examples/#todomvc)をつくる全9回のチュートリアル解説です。本稿はその中から、新構文やVue 3で変わった点を拾い出してご紹介します。記事の順番に沿ったピックアップですので、体系的でも網羅的でもありません。それでもお役に立てばということでご説明します。

まず全体として、テンプレートのコンポーネントタグにはパスカルケースが強く推奨されるようになったことにご注意ください。

> ケバブケースの`<my-component>`も同じようにテンプレートで動作します。しかし、一貫性を保つために、パスカルケースのコンポーネントタグを強く推奨します。これはネイティブのカスタム要素と区別するのにも役立ちます。
(「[コンポーネントの使用](https://v3.ja.vuejs.org/api/sfc-script-setup.html#%E3%82%B3%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%BF%E7%94%A8)」)

# 01 リスト項目の表示と追加
初回の「[TodoMVC 01 リスト項目の表示と追加](https://www.fumiononaka.com/Business/html5/FN2211001.html)」は基礎的な操作を扱いましたので、項目数が比較的多いです。具体的な使い方やコード例は、チュートリアル記事をご覧ください。

### Vueのひな形プロジェクトはViteを使ってつくる
Vueのひな形プロジェクトをつくる公式ツールとして、これまではVue CLIが使われてきました。けれど、現在はメンテナンスモードとなっており、[Vite](https://vitejs.dev/)(ヴィット: フランス語で[素早い](https://kotobank.jp/frjaword/vite)の意)を用いることが推奨されています。

```terminal
npm init vue@latest
```

> Vue CLIは、Vueのための公式のwebpackベースのツールチェーンです。現在はメンテナンスモードで、特定のwebpackのみの機能に依存していない限り、新しいプロジェクトはViteで始めることを推奨します。ほとんどの場合においてViteは優れた開発経験を提供します。
(「[Vue CLI](https://ja.vuejs.org/guide/scaling-up/tooling.html#vue-cli)」)

### コンポーネントのリアクティブな値は`ref`オブジェクトで定める
コンポーネントのリアクティブな値(`ref`オブジェクト)を定めるのは[`ref()`](https://ja.vuejs.org/api/reactivity-core.html#ref)メソッドです(「[ref](https://v3.ja.vuejs.org/api/refs-api.html#ref)」参照)。`ref`オブジェクトのTypeScriptによる型づけは、メソッドの引数に渡した初期値から推論されます。値が単純なプリミティブでなくオブジェクトや複雑な型の場合には、`ref()`の呼び出し時に型引数を用いるとよいでしょう(「[ref()の型付け](https://ja.vuejs.org/guide/typescript/composition-api.html#typing-ref)」)。

なお、`ref`オブジェクトに収められた値を参照するとき、`<script>`ブロック内では`value`プロパティから取り出さなければなりません。けれど、`<template>`は自動的に`value`プロパティ値にアクセスします。したがって、`<template>`内では`ref`オブジェクトの`value`プロパティは参照しないことにご注意ください(「[テンプレートでの使用](https://v3.ja.vuejs.org/guide/composition-api-setup.html#%E3%83%86%E3%83%B3%E3%83%95%E3%82%9A%E3%83%AC%E3%83%BC%E3%83%88%E3%81%A6%E3%82%99%E3%81%AE%E4%BD%BF%E7%94%A8)」)。

### 親コンポーネントへのイベントの送信には`defineEmits()`を用いる
親コンポーネントへのイベントの送信(`emit`)を定めるのは、[`defineEmits()`](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)です。「`defineEmits`は、`<script setup>`内でのみ使用可能なコンパイラーマクロです。インポートする必要はなく、`<script setup>`が処理されるときにコンパイルされます」(「[defineProps() & defineEmits()](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)」)。`defineEmits()`の宣言に渡すのは、リテラルの型引数です(「[TypeScriptのみの機能](https://ja.vuejs.org/api/sfc-script-setup.html#typescript-only-features)」)。戻り値は送信(`emit`)するための関数で、引数オブジェクトにイベントとデータを収めて渡せば、親コンポーネントが受け取ります。

### `Event`型の`target`プロパティには`value`プロパティが備わっていない
`<input>`要素のイベントハンドラでは、引数のイベントの型づけは`Event`です。ところが、この場合の`target`プロパティの型は`EventTarget`で、`value`プロパティが備わっていません。そのため、`instanceof`演算子により、`target`が`<input>`要素(`HTMLInputElement`型)であることを限定します。こうして、`value`プロパティ値の読み書きができるようになるのです。

### 親コンポーネントから受け取るプロパティは`defineProps()`で型宣言する
親コンポーネントから渡されたプロパティを受け取るには、子コンポーネントが型を宣言しておかなければなりません。そのときに用いるのは、`defineProps()`です。構文は`defineEmits()`と似ていて、`<script setup>`構文でのみ使えるコンパイラーマクロです(「[defineProps() & defineEmits()](https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)」)。`import`は要りません。リテラルの型引数を渡してください(「[TypeScriptのみの機能](https://ja.vuejs.org/api/sfc-script-setup.html#typescript-only-features)」)。`<script setup>`が処理されるときにコンパイルされます。


# 02 リスト項目の削除とスタイル変更
「[TodoMVC 02 リスト項目の削除とスタイル変更](https://www.fumiononaka.com/Business/html5/FN2211002.html)」からご紹介する項目はひとつでした。

### 親から受け取ったプロパティは`<script setup>`ブロックから`defineProps()`関数の戻り値で参照する
親コンポーネントから受け取ったプロパティを`<script setup>`ブロックから参照するとき用いるのが、[`defineProps()`関数の戻り値](https://v3.ja.vuejs.org/api/sfc-script-setup.html#defineprops-%E3%81%A8-defineemits)です。各値は戻り値オブジェクトのプロパティとして取り出せます。


# 03 算出プロパティとデータのローカルへの保存
「[TodoMVC 03 算出プロパティとデータのローカルへの保存](https://www.fumiononaka.com/Business/html5/FN2212001.html)」からは、3つご説明します。


### 算出プロパティ`computed()`を使う
リアクティブなデータの含まれるロジックから、結果を導いて返すのが「[算出プロパティ](https://ja.vuejs.org/guide/essentials/computed.html)」です。関数[`computed()`](https://ja.vuejs.org/api/reactivity-core.html#computed)を用い、算出された`ref`オブジェクトが返されます。引数はgetter関数です。[`computed()`の型づけ](https://ja.vuejs.org/guide/typescript/composition-api.html#typing-computed)は戻り値から推論されますので、通常は気にしなくてかまいせん。算出プロパティは、自動的にリアクティブな依存関係を追跡します。

### Vue 3ではfiltersがサポートされない
Vue 3ではfiltersはサポートされません(「[3.x での更新](https://v3.ja.vuejs.org/guide/migration/filters.html#_3-x-%E3%81%A6%E3%82%99%E3%81%AE%E6%9B%B4%E6%96%B0)」)。代わりに、算出プロパティを用いることが推奨されています。

### データの変更をリアクティブに追跡する`watchEffect()`(ウォッチャー)
データの変更をリアクティブに追跡するのが[`watchEffect()`](https://ja.vuejs.org/api/reactivity-core.html#watcheffect)です。`watchEffect()`の引数には実行するエフェクト関数(コールバック)を渡します。算出プロパティと同じように、リアクティブな依存先を自動的に追跡して、副作用がすぐに実行されるのです(「[watchEffect()](https://ja.vuejs.org/guide/essentials/watchers.html#watcheffect)」参照)。


# 04 リスト表示する項目を選び出して切り替える
「[TodoMVC 04 リスト表示する項目を選び出して切り替える](https://www.fumiononaka.com/Business/html5/FN2212002.html)」では、つぎのフックをご紹介しています。

### コンポーネントがマウントされたあと呼び出される`onMounted`フック
コンポーネントがマウントされたあとに呼び出されるコールバックを定めるのが[`onMounted`](https://ja.vuejs.org/api/composition-api-lifecycle.html#onmounted)フックです。フックは通常つぎの場合に使われます。

* コンポーネントのレンダリングされたDOMにアクセスする必要がある副作用を実行する場合。
* サーバーレンダリングされるアプリケーションでDOM関連のコードをクライアントに限定する場合。


# 05 チェックをまとめてオン/オフしたり削除する
「[TodoMVC 05 チェックをまとめてオン/オフしたり削除する](https://www.fumiononaka.com/Business/html5/FN2212003.html)」では、新構文やVue 3の新しい仕様に関わるご説明はありませんでした。


# 06 項目のテキストをダブルクリックで再編集する
「[TodoMVC 06 項目のテキストをダブルクリックで再編集する](https://www.fumiononaka.com/Business/html5/FN2212004.html)」では、新構文やVue 3の新しい仕様に関わるご説明はありませんでした。


# 07 フォーカスをコントロールする
「[TodoMVC 07 フォーカスをコントロールする](https://www.fumiononaka.com/Business/html5/FN2212005.html)」では、カスタムディレクティブについて解説しました。

### カスタムディレクティブを使う
コンポーネントと同じライフサイクルに応じて、DOM要素を扱うのが「[カスタムディレクティブ](https://ja.vuejs.org/guide/reusability/custom-directives.html)」です。要素に加えるカスタムディレクティブには、接頭辞`v-`につづけて任意の名前を定めます。呼び出すフックオブジェクトの変数は、`<script setup>`構文では、ハイフン(-)は外して`v`で始まるキャメルケースをお使いください。フックオブジェクトには、ライフサイクルに応じたフック関数が加えられます(「[ディレクティブフック](https://ja.vuejs.org/guide/reusability/custom-directives.html#directive-hooks)」参照)。なお、カスタムディレクティブは、DOMを直接扱わなければならない場合にかぎって使うのがよいでしょう。


# 08 要素にアニメーションを加える
「[TodoMVC 08 要素にアニメーションを加える](https://www.fumiononaka.com/Business/html5/FN2301001.html)」でご紹介したのは、Vue 2からVue 3で加えられた後方互換性を破る仕様変更です。

### 後方互換性を破るトランジションクラスの変更
[`<Transition>`](https://ja.vuejs.org/guide/built-ins/transition.html#the-transition-component)コンポーネントを用いたトランジションクラスについて、Vue 2からVue 3で後方互換性を破るつぎのような名前の変更が行われました。`-to`に対して`-from`が新たに定義されたことにより、CSSの記述はわかりやすくなったでしょう。けれど、後方互換性がなくなることにご注意ください。

> `v-enter`トランジションクラスは`v-enter-from`へ、そして`v-leaveト`ランジションクラスは`v-leave-from`へと名前が変更されました(「トランジションクラスの変更」)。
(「[トランジションクラスの変更](https://v3.ja.vuejs.org/guide/migration/transition.html)」)


# 09 コンポーネントからロジックをコンポーザブル関数に切り分ける
「[TodoMVC 09 コンポーネントからロジックをコンポーザブル関数に切り分ける](https://www.fumiononaka.com/Business/html5/FN2301002.html)」で解説したのは、 Composition APIのコンポーザブル関数です。具体的なコード例については、チュートリアル記事をお読みください。

### コンポーザブル関数でコンポーネントのロジックを切り分ける
「[コンポーザブル](https://ja.vuejs.org/guide/reusability/composables.html)」と呼ばれる関数を用いれば、コンポーネントからロジックが切り分けられます。「『コンポーザブル（composable）』とは、Vue の Composition APIを活用して状態を持つロジックをカプセル化して再利用するための関数です」(「[『コンポーザブル』とは？](https://ja.vuejs.org/guide/reusability/composables.html#what-is-a-composable)」)。状態の変数(オブジェクトの参照)とそれらを操作するための関数(メソッド)が備わります。コンポーザブルを呼び出したコンポーネントが戻り値として受け取るのはこうした変数や関数です。すると、コンポーネントは状態から切り離されて、ロジックが分けられます。

***

「Vue + `<script setup>` + TypeScript: TodoMVCチュートリアルシリーズ」から、新構文やVue 3で変わった点を拾い出してご紹介しました。体系立った取り上げ方ではなかったものの、ひとつの作例をつくり上げる中で注目された項目です。構文それぞれの使い方やコード例については、ぜひ本チュートリアルシリーズをお読みください。

## Vue + `<script setup>` + TypeScript: TodoMVCチュートリアルシリーズ

* [TodoMVC 01 リスト項目の表示と追加](https://www.fumiononaka.com/Business/html5/FN2211001.html)
* [TodoMVC 02 リスト項目の削除とスタイル変更](https://www.fumiononaka.com/Business/html5/FN2211002.html)
* [TodoMVC 03 算出プロパティとデータのローカルへの保存](https://www.fumiononaka.com/Business/html5/FN2212001.html)
* [TodoMVC 04 リスト表示する項目を選び出して切り替える](https://www.fumiononaka.com/Business/html5/FN2212002.html)
* [TodoMVC 05 チェックをまとめてオン/オフしたり削除する](https://www.fumiononaka.com/Business/html5/FN2212003.html)
* [TodoMVC 06 項目のテキストをダブルクリックで再編集する](https://www.fumiononaka.com/Business/html5/FN2212004.html)
* [TodoMVC 07 フォーカスをコントロールする](https://www.fumiononaka.com/Business/html5/FN2212005.html)
* [TodoMVC 08 要素にアニメーションを加える](https://www.fumiononaka.com/Business/html5/FN2301001.html)
* [TodoMVC 09 コンポーネントからロジックをコンポーザブル関数に切り分ける](https://www.fumiononaka.com/Business/html5/FN2301002.html)