Vue3.3 新機能リリース!便利になった機能を紹介

シェアしてね〜🤞

2023/5/11 に、vue 3.3 がリリースされました。 https://blog.vuejs.org/posts/vue-3-3#generic-components

Vue3.3 では、TypeScript 関連の強化や便利な機能などが追加されています。 従来よりも型宣言がしやすく、かつ vue 自体も書きやすくなっているため、開発体験の向上が図れると感じています。

今回の記事では、新機能についての内容と、その中で便利だと感じた点を紹介していきます。

以下は新機能の一覧です。

  • <script setup> + TypeScript DX Improvements(script setup 構文における TypeScript 体験の向上)

    • Imported and Complex Types Support in Macros(外部インポートされた複合の型サポート)
    • Generic Components(コンポーネントでのジェネリック型パラメータのサポート)
    • More Ergonomic defineEmits(人間工学的な宣言)
    • Typed Slots with defineSlots(型付き slot の宣言)
  • Experimental Features(実験的機能)

    • Reactive Props Destructure(props の応答性維持とデフォルト宣言の簡易化)
    • defineModel(defineModel を用いた v-model 宣言の簡易化)
  • Other Notable Features(その他の注目すべき機能)

    • defineOptions(コンポーネントオプション の記述簡易化)
    • Better Getter Support with toRef and toValue(toRef, toValue: getter のサポート)
    • JSX Import Source Support(JSX の Import Source のサポート)

<script setup> + TypeScript DX Improvements(script setup 構文における TypeScript 体験の向上)

大きな変更点としては、TypeScript のサポートがより行われた点となります。

それでは見ていきましょう。

Imported and Complex Types Support in Macros(外部インポートされた複合の型サポート)

以前は、defineProps および defineEmits の型パラメーターの位置で使用される型はローカル型に限定されており、型リテラルとインターフェースのみがサポートされていました。

以下のようにvueファイル内に直接Interface定義しなければなりませんでした。

<script setup lang="ts">
interface Props {
  name: string
  kana: string
  email: string
}

defineProps<Props>()
</script>

この制限は 3.3 で解決されました。コンパイラは外部インポートされた型を解決できるようになり、限られた複合型のセットをサポートするようになりました。

便利になった点としては、以前のようにローカル型で型定義しなくても良くなったので、共通のインターフェースを別のコンポーネントや他tsファイルから使用できるようになりました。

型定義に関する事は、ts ファイル側で管理できるのも良い点です。

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

// 他tsファイルからimportしたinterface + nicknameの型の追加
defineProps<Props & { nickname?: string }>()
</script>

Generic Components(コンポーネントでのジェネリック型パラメータのサポート)

<script setup> を使用するコンポーネントは、ジェネリック属性を介してジェネリック型パラメータを受け入れることができるようになりました。

ここで従来の例を見てみましょう。

以下の場合を想定します。

type Item を継承したItem1やItem2を、propsの値として親コンポーネントから子コンポーネントへ渡せるようにしたい

これを実現したい場合、以下の記載方法となります。

<script setup lang="ts">
import type { Item1, Item2 } from './types'
interface Props {
  list: Array<Item1 | Item2>
}

defineProps<Props>()
</script>

従来の例だと、Propsのlistを定義する際、ジェネリック型を使って縛ることが出来ないため、

type Item1, type Item2 をユニオン型で定義する必要があり、Itemを継承したtypeの数分の型定義が必要となってしまいます。

しかし、今回のアップデートにより、この問題は解決できます。

以下は、ジェネリック型にユニオン型の制約や、インターフェイス継承の制約をつける場合の例となります。

<script setup lang="ts" generic="U extends Item">
import type { Item } from './types'
defineProps<{
  list: U[]
}>()
</script>

ジェネリック型で定義できると component の中身がスッキリする事が一目見てわかると思います。

この例から、listについて、ジェネリック型を使って縛ることが出来るので、自らtype Item1、type Item2それぞれのユニオン型を作る手間がなくなったり、可読性が増したりします。

defineEmits(人間工学的な宣言)

以前の defineEmits の構文です。

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

少し冗長で書きにくかったので、Vue3.3 では、より人間工学に基づいた書きやすい構文が導入されました。

以前の構文ではe: にemitのイベント名を記載する形でしたが、イベント名の数だけe: を記載する事になるため、少し冗長な記載方法になっていました。

Vue3.3では、emitのイベント名をキーとした以下の構文で記載する事ができるようになったため、以前の冗長さは解消されました。

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

Typed Slots with defineSlots(型付き slot の宣言)

新しい defineSlots マクロを使用して、スロットの型を宣言できます。

これまでは、スロットで受け取る引数に型を宣言できなかったため、より型システムの恩恵を受ける事ができるようになりました。

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

Experimental Features(実験的機能)

まだ実験的な機能ではありますが、今後の安定版が期待できる機能と感じていますので紹介します。

Reactive Props Destructure(props の応答性維持とデフォルト宣言の簡易化)

Vue3.3 以前では、propsのreactive値の変化の検知やデフォルト値宣言の際は、以下のような記述が必要でした。

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

interface Props {
  msg: string
}

// デフォルト値を指定するためには、withDefaultsを使用する必要がある
const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
})

watchEffect(() => {
  // 親コンポーネントから子コンポーネントに渡したreactive値の変化を検知するためには、propsオブジェクトを介して値にアクセスする必要がある
  console.log(`msg is: ${props.msg}`)

Vue3.3 では、以下の様にシンプルな形で記述できるようになります。

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

// withDefaultsを使用せずともデフォルト値の指定が可能となった
const { msg = 'hello' } = defineProps(['msg'])

watchEffect(() => {
  // propsオブジェクトを介さずとも、`msg` と記述するだけで値の変化に追従できるようになった
  console.log(`msg is: ${msg}`)
})
</script>

利用には以下の設定が必要となります。

export default {
  plugins: [
    vue({
      script: {
        propsDestructure: true
      }
    })
  ]
}

defineModel(v-model 宣言の簡易化)

従来は v-model との双方向バインディングを行うためには、

従来は、親コンポーネントからreactive値を子コンポーネントに渡して、状態を変化させるためには、props と emit を用いて表現する必要がありました。

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)

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

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

Vue3.3 では、新しい defineModel を使用する事で、シンプルな記述にする事ができるため、便利な機能でしょう。

<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>

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

まとめ

ここまで新機能についての内容説明と、便利だと感じた点を紹介してきましたが、

特に、<script setup> + TypeScript DX Improvements(script setup 構文における TypeScript 体験の向上) の機能追加項目に関してはメリットが大きく、開発体験が大きく変わると感じています。

皆さんも、是非とも開発に取り入れてみましょう!

シェアしてね〜🤞