TypeScriptが強化されたVue3.3アップデートと将来の機能について

ついに待望のVue3.3がリリースされました！
Vue.jsのリリースにはアニメのコードネームがアルファベット順に付けられていますが、今回は「Rurouni Kenshin（るろうに剣心）」との事です。外国では「るろうに」は翻訳されていないようですね（一部では「Samurai X」という名前も使われているそうです）。

今回の更新ではTypeScript関連の強化や便利な機能などが追加されています。公式のブログにVue3.3の変更に関して網羅的にまとめられているので、詳細な部分はそちらをご参照ください。（本記事も当該のブログをベースに記載しています）

https://blog.vuejs.org/posts/vue-3-3

## Vue3.3概要
- Reactivity Transform非推奨
- defineProps: 外部インポートされた複合の型サポート
- defineEmits: 人間工学的な宣言
- defineSlots: 型付きslotの宣言
- defineOptions: コンポーネントoptionの記述簡易化
- ジェネリック型パラメータのサポート
- toRef, toValue: getterのサポート
- JSXのImport Sourceのサポート
- (実験的機能) Reactive Props Destructure: propsの応答性維持とデフォルト宣言の簡易化
- (実験的機能) defineModel: `v-model`宣言の簡易化

### Reactivity Transform非推奨
2023年の2月頃に「Reactivity Transform R.I.P」との訃報が届き、RFCもドロップとなってしまった機能ですが、Vue3.3からは非推奨、3.4で削除となります。現在利用されている方で、引き続き利用を検討されている方は[Vue Macros](https://vue-macros.sxzz.moe/)を利用してください。今回の更新の多くはVue Macros、そして作者の[sxzz](https://github.com/sxzz)さんの貢献が大きく占めています。比較的最近Vue.jsのコアチームに入った方で最年少とのこと。素晴らしい才能ですね。

### defineProps: 外部インポートされた複合型サポート
長年issueに上がっていた`defineProps`における外部インポートされた複合型サポートの問題が解消されました。今まではオブジェクトでの対応などが必要でしたが、以下の様なインポートをしても解決することが出来ます。

```vue
<script setup lang="ts">
import type { Props } from './foo'

// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>
```

ただし、Conditional Typesなどを利用した一部の特殊なケースはサポートされていないためご注意ください。

### defineEmits: 人間工学的な宣言
Vue3.2以前の`defineEmits`は少し理解しづらい記述方法となっていましたが、3.3ではより人間工学に則した記述方法がサポートされています。3.2以前のものも引き続き利用可能です。

```ts
// 3.2以前
const emit = defineEmits<{
  (e: 'foo', id: number): void
  (e: 'bar', name: string, ...rest: any[]): void
}>()

// 3.3以降
const emit = defineEmits<{
  foo: [id: number]
  bar: [name: string, ...rest: any[]]
}>()
```

### defineSlots: 型付きslotの宣言
掲題のとおり、型付のslotの宣言が可能となります。

```vue
<script setup lang="ts">
defineSlots<{
  default?: (props: { msg: string }) => any
  item?: (props: { id: number }) => any
}>()
</script>
```

### defineOptions: コンポーネントoptionの記述簡易化
`name`や`inheritAttrs`などのoptionは`setup`シンタックスでは記述することが出来ず、別途scriptタグを記述する必要があったのですが、この度不要となりました。`setup`のようにnameなどをタグに記載するという案もRFCでは出ていたのですが、個人的にはこちらの方が一貫した表現で好きです。

```vue
<script setup>
defineOptions({ inheritAttrs: false })
</script>
```

### ジェネリック型パラメータのサポート
`script setup`でジェネリック型が利用できるようになりました。下記の例ではitemsが`string[]`ならばselectedを`number`など`string`以外にすると怒られます。

```vue
<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
  selected: T
}>()
</script>
```

複数の宣言、`extends`なども利用可能です。

```vue
<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
  id: T
  list: U[]
}>()
</script>
```

### toRef, toValue: getterのサポート
`toRef`ではRef化する際に.valueのアクセスに対してgetterを呼ぶ処理を記述することが可能となります。内容は以下のコードとコメントのとおりとなります。`computed`に近いですが、getterを呼ぶだけのコストが低いアクセスが可能です。

```ts
// ref(1)と同様
toRef(1)
// .valueのアクセスの際にgetterを呼ぶreadonlyのRefを作成
toRef(() => props.foo)
// 元々Refなため変更なし
toRef(existingRef)
```

`toValue`も同様にgetterがサポートされます。

```ts
toValue(1) //       --> 1
toValue(ref(1)) //  --> 1
toValue(() => 1) // --> 1
```

### JSXのImport Sourceのサポート
Vue3.3以降ではTypeScriptの`jsxImportSource`オプションを介したJSXの名前空間の指定がサポートされます。3.3まではグローバルな名前空間のまま、3.4ではグローバルから削除されるため、3.3から利用される方は`tsconfig.json`の`jsxImportSource`オプションを設定してください。

また、今回の更新であるジェネリック型などもJSXでサポートされています。

### (実験的機能) Reactive Props Destructure: propsの応答性維持とデフォルト宣言の簡易化
propsの応答性維持は`toRefs`などを利用していたと思いますが、以下の様にシンプルな形で記述することが可能となります。デフォルト値も`withDefaults`が不要となりシンプルな記述となります。

```vue
<script setup>
import { watchEffect } from 'vue'

const { msg = 'hello' } = defineProps(['msg'])

watchEffect(() => {
  // accessing `msg` in watchers and computed getters
  // tracks it as a dependency, just like accessing `props.msg`
  console.log(`msg is: ${msg}`)
})
</script>

<template>{{ msg }}</template>
```

実験的な機能なため利用には注意してください。また利用には以下の設定が必要です。

```ts
// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        propsDestructure: true
      }
    })
  ]
}
```

### (実験的機能) defineModel: `v-model`宣言の簡易化
Vue3.2以前ではコンポーネント側で双方向バインディングを行う際は、以下の様にpropsとemitsの処理を両方記述する必要がありました。

```vue
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

function onInput(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="modelValue" @input="onInput" />
</template>
```

Vue3.3以降の記載は以下の様になります。かなりシンプルになりました。

```vue
<script setup>
const modelValue = defineModel()
</script>

<template>
  <input v-model="modelValue" />
</template>
```

Vue.jsのユーティリティライブラリであるVueUseでは[useVModel](https://vueuse.org/core/useVModel/)という関数が記述を楽にしてくれていましたが、こちらは公式のサポートとなります。ただし、こちらも実験的機能なため利用には注意が必要であり、かつ以下の設定が必要です。

```ts
// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        defineModel: true
      }
    })
  ]
}
```

### その他
以下の関連ライブラリもバージョンアップが推奨されています。利用されている場合は併せて行いましょう。
- volar / vue-tsc@^1.6.4
- vite@^4.3.5
- @vitejs/plugin-vue@^4.2.0
- vue-loader@^17.1.0 (if using webpack or vue-cli)

## Vue3.4以降の変更予定
### Suspenseのstable化
ローディングなど非同期の依存解消などに利用される組み込みコンポーネントですが、現在は実験的機能のまま止まっています。Vue3.4で取り組まれる予定とのこと。（公式Twitterより）

### SSR lazy hydration
AstroのアイランドアーキテクチャやQwicなどを受けて発展させた概念であり、SSRのハイドレーションを最適化する方法です。詳細は続報を待ちましょう。

### Vapor Mode
Solidに触発されて考えられた概念です。Virtual DOMベースの代替となりパフォーマンス向上、メモリ削減、バンドルサイズ削減といい事づくめの機能です。[Vue.js](https://blog.vuejs.org/posts/2022-year-in-review)のブログによると今年後半に続報が出るかも・・・とのこと。