Vue3.4リリース速報

## はじめに
2023年12月28日にVue3.4がリリースされました。
パフォーマンス向上、実験的機能の一部安定化、短縮表記の追加など、
すぐにアップデートしたいと思う内容でした。

また、リリースバージョンには日本のアニメの名前が名付けられているようです。
3.3では「Rurouni Kenshin」でしたが、3.4では「🏀 Slam Dunk」との事です。
すごい親近感を覚えました。

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

今回、リリースノートの内容に加えて、GithubのPRや公式ドキュメントから該当箇所をピックアップして、記事にまとめた形になります。

## 依存関係のアップデート
Vue3.4にアップデートするにあたり、依存関係のアップデートが必要です。

- Volar / vue-tsc@^1.8.27 (必須)
- @vitejs/plugin-vue@^5.0.0 (Vite を使用している場合)
- nuxt@^3.9.0 (Nuxt を使用している場合)
- vue-loader@^17.4.0 (webpack または vue-cli を使用する場合)

また、Vueでtsxを使用している場合や、3.3で非推奨の機能を使用している方は、
以下を確認の上対応が必要です。
[global-jsx-namespace](https://blog.vuejs.org/posts/vue-3-4#global-jsx-namespace)
[other-removed-features](https://blog.vuejs.org/posts/vue-3-4#other-removed-features)

## 本題

アップデート一覧です。

- [パーサーとSFCビルドのパフォーマンス向上](#パーサーとsfcビルドのパフォーマンス向上)
- [リアクティブなプロパティの再計算効率向上](#リアクティブなプロパティの再計算効率向上)
- [defineModelの安定化](#definemodelの安定化)
- [v-bindにおける同名短縮表記](#v-bindにおける同名短縮表記)
- [Hydration Mismatch エラーの改善](#hydration-mismatch-エラーの改善)
- [エラーコードやコンパイル時の設定方法に関するリファレンス追記](#エラーコードやコンパイル時の設定方法に関するリファレンス追記)

それでは、一つずつ見ていきます

### パーサーとSFCビルドのパフォーマンス向上

#### パーサーについて
Vue3.4 では、テンプレートのパーサーを根本から書き直したとの事です。
同じテンプレートでも、**半分の時間** で解析するとの事です。
詳しくは[ベンチマーク(PR#9674)](https://github.com/vuejs/core/pull/9674)に記載がありますのでご確認ください。

#### SFCビルドについて

[ベンチマーク(PR#9674)](https://github.com/vuejs/core/pull/9674)では、ソースマップの生成中にVue SFC のスクリプトとテンプレート部分をコンパイルする際に、**最大 44%** の向上結果が得られたと示されています。
Vue3.4ではSFCを使用するほとんどのプロジェクトのビルドが高速化されるはずです。

注）SFCファイル解析とJavaScriptおよびソースマップへの変換にかかる時間のみを測定した結果であるため、CSSの処理、JavaScriptバンドル、縮小化は含まれません。なので、上記の数字はプロジェクト全体のビルド時間削減を表したものではないとの注意書きがありました。そうはいってもやはり、顕著な結果は得られるとの事です。


### リアクティブなプロパティの再計算効率向上

3.4 より前では、computedなどのリアクティブなプロパティは、「計算結果が同じ」であっても再計算を行います。

```js
// before

const count = ref(0)
const isEven = computed(() => count.value % 2 === 0)

watchEffect(() => console.log(isEven.value)) // logs true

count.value = 2 // computedの値が再計算される & watchEffectのコールバックが呼ばれる
```
この例では、countの値が0から2に更新された事をトリガーとして、computedの計算結果がtrueで同じであっても、computedの値が再計算され、watchEffectのコールバックが呼ばれる事を表しています。

3.4 以降の最適化により、計算結果が実際に変更された場合にのみコールバックが起動されるようになりました。
つまり、上記例で言うと、countの値が0から2に更新されても、computedは再計算を行わないので、watchEffectのコールバックも呼ばれない、という事となります。

```js
// after

const count = ref(0)
const isEven = computed(() => count.value % 2 === 0)

watchEffect(() => console.log(isEven.value)) // logs true

count.value = 2 // computedの値が再計算されない & watchEffectのコールバックも呼ばれない
```

Jonson Chu氏の[Xの投稿](https://twitter.com/johnsoncodehk/status/1695383715906744449?s=12)がを見ると改善点がわかりやすいです。

[ベンチマーク](https://github.com/vuejs/core/pull/5912#issuecomment-1748985641)では、computed、watch、watchEffectそれぞれの結果が記されているので興味がある方はご確認ください。


### defineModelの安定化

Vue3.3の実験的機能として追加された新しいマクロであるdefineModelですが、
Vue3.4では安定した状態になりました。
また、v-model修飾子を使用した場合のサポートも強化されました。

defineModelについて、記載方法は以下です。

```Child.vue
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>parent bound v-model is: {{ model }}</div>
</template>
```

```Parent.vue
<Child v-model="count" />
```

親値と子値の間の双方向バインディングとして機能します。
また、defineModelはrefと同様にして、値にアクセス・変更が可能です。

Vue3.2以前での記載方法は以下でした。
```Child.vue
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>
```
よって、defineModelを用いる事で、だいぶ記述量が少なくなる事が見て取れます。

#### v-model引数
v-modelでは引数を受け入れることもできます。

```Parent.vue
<Child v-model:title="bookTitle" />
```

defineModel()子コンポーネントでは、最初の引数として文字列を渡すことで、対応する引数をサポートできます。

```Child.vue
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>
```

#### 複数のv-modelバインディング
単一のコンポーネントインスタンスに複数のv-modelバインディングを作成できるようになりました。
上記述べた、引数を設定する場合に関してのみ使える機能です。

```Parent.vue
<Child
  v-model:first-name="first"
  v-model:last-name="last"
/>
```

```Child.vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>
```

#### v-model修飾子の処理
v-modelには修飾子をつける機能があります。
これにより、バインディング値のカスタマイズまたは条件をつけれる事を、ご存知でしたでしょうか？

詳細については[こちら](https://vuejs.org/guide/essentials/forms#modifiers)ご確認ください。

#### capitalize
一例としてcapitalizeが紹介されています。

```Parent.vue
<Child v-model.capitalize="myText" />
```
defineModelの戻り値を以下のように構造化することで、
v-modelは子コンポーネントのmodifiersにアクセスできます。
```Child.vue
<script setup>
const [model, modifiers] = defineModel()

console.log(modifiers) // { capitalize: true }
</script>

<template>
  <input type="text" v-model="model" />
</template>
```

また、値の読み取り/書き込み方法を条件付きで調整するには、
getとsetオプションをdefineModelに渡すことができます。

モデル参照時に値を受け取り、変換後の値を返す必要があります。

```Child.vue
<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>
```

#### v-model引数付きの修飾子

v-model引数、かつ、v-model修飾子の処理、かつ、複数のv-modelバインディング
を使用する場合についてです。
単純にこれまでの説明を掛け合わせた形となります。

```Parent.vue
<Child
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
```

```Child.vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true}
</script>
```

v-model、defineModelに関する参考ドキュメント：
[Revised Component v-model section](https://vuejs.org/guide/components/v-model.html)
[defineModel API reference](https://vuejs.org/api/sfc-script-setup.html#definemodel)

### v-bindにおける同名短縮表記

propNameを短縮できるようになりました。
```vue
<template>
  <!-- before -->
  <img :id="id" :src="src" :alt="alt">
</template>
```

```vue
<template>
  <!-- after -->
  <img :id :src :alt>
</template>
```

式が定義されていない場合は、内部的にはbeforeと同様の式が生成されるようです。
[PR#9451](https://github.com/vuejs/core/pull/9451)

### Hydration Mismatch エラーの改善
**そもそもHydrationとはなんぞや？**

SSRを実行する場合、クライアント側で全てのレンダリングを行うのではなく、サーバー側で事前に静的なHTMLを生成してクライアントに送信します。
これによりSPAと比べて、初期表示を高速化できるのですが、HTMLだけを送信しただけでは、クリック後の動作などのインタラクションができません。

HTMLレンダリング後、クライアント側でJavaScriptをダウンロードし、HTMLと結びつける事で、インタラクションが可能になります。

このHTMLにJavascriptを結びつけてインタラクティブにするプロセスをHydrationと言います。


**では、どういった時にHydration Mismatchエラーが起きるのか？**

`サーバー側とクライアント側でレンダリングされるテキストに差が生じた時に発生します`

Vue3.4では、このエラーに関しての改善がいくつかなされたようです。

- 文言の明瞭さの向上 (サーバーによってレンダリングされたものとクライアント上で期待されるもの)
- メッセージには問題の DOM ノードが含まれるようになり、ページまたは要素パネルですぐに DOM ノードを見つけやすくなった
- Hydrationの不一致チェックは、クラス、スタイル、およびその他の動的にバインドされた属性にも適用されるようになった
- 新しいコンパイル設定 [`__VUE_PROD_HYDRATION_MISMATCH_DETAILS__`](https://vuejs.org/api/compile-time-flags.html#vue-prod-hydration-mismatch-details) が追加。これを使用すると、本番環境でもHydration不一致エラーに関する詳細を含めることができるようになった





### エラーコードやコンパイル時の設定方法に関するリファレンス追記

バンドルサイズを削減するために、Vue は実稼働ビルドで長いエラーメッセージの文字列を削除します。
その反面、運用環境で検出されたエラーコードが短くなり、Vueのソースコードを調べなければ解読が難しいという事を意味します。

これを改善するために、
[Production Error Reference Page](https://vuejs.org/error-reference/)をドキュメントに追加したとの事です。
エラーコードは、Vue安定版リリースの最新バージョンから自動的に生成されるとの事です。

また、さまざまなビルドツールに対しての設定方法を説明する、
[Compile-Time Flags Reference](https://vuejs.org/api/compile-time-flags.html)も追加したとの事です。

## 最後に
現在進行中のnuxt3を使用したプロジェクトで、Vue3.4（`nuxt@^3.9.0`）にアップデートしてみたところ、特にアプリケーションの挙動に問題がなかったので大丈夫そうです。
実験的機能だったdefineModelの警告も出なくなりました。
皆さんも是非！