🚀

Vue 3.5: パフォーマンスと開発体験を革新する包括的アップデート

2024/09/25に公開

はじめに

2024年9月1日に Vue 3.5 がリリースされました。本記事では、Vue 3.5 で導入された新機能と主要な改善点について詳しく解説します。

公式ブログも合わせてご覧ください:

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

Vue 3.5 の主な新機能

  1. Reactive Props Destructure
  2. 新しい API の導入
    • useTemplateRef()
    • onWatcherCleanup()
    • カスタム要素関連: useHost(), useShadowRoot()
  3. Deferred Teleport

Vue 3.5 の主な機能改善

  1. Reactivity System の改善
  2. SSRの改善
    • Lazy Hydration
    • useId()
    • data-allow-mismatch
  3. Custom Elements の改良

それでは、各機能について詳しく見ていきましょう。

新機能の詳細

1. Reactive Props Destructure の安定化

<script setup> 内で defineProps から分割代入された変数が自動的にリアクティブになる機能が追加されました。これにより、コードがより簡潔になり、可読性が向上します。

詳細な説明は以下のブログ記事をご覧ください:

https://zenn.dev/comm_vue_nuxt/articles/reactive-props-destructure

RFC

https://github.com/vuejs/rfcs/discussions/502

使用例

Vue 3.5 以前:
<script setup lang="ts">
import { computed } from 'vue'

const props = withDefaults(defineProps<{
  count?: number
}>(), {
  count: 0
})

const double = computed(() => props.count * 2)
</script>

<template>
  <div>Count: {{ props.count }}</div>
  <div>Double: {{ double }}</div>
</template>
Vue 3.5 以降:
<script setup lang="ts">
import { computed } from 'vue'

const { count = 0 } = defineProps<{
  count?: number
}>()

const double = computed(() => count * 2)
</script>

<template>
  <div>Count: {{ count }}</div>
  <div>Double: {{ double }}</div>
</template>

この新しい書き方では、withDefaults を使用せずにデフォルト値を設定できるようになりました。また、プロパティへのアクセスが直接的になり、コードの可読性が向上しています。

2. 新しい API の導入

useTemplateRef()

useTemplateRef() を使ってテンプレート参照を取得する新しい方法が導入されました。これにより、動的に変更される ID への ref バインディングがサポートされるようになりました。

使用例:

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

const inputRef = useTemplateRef('input')
</script>

<template>
  <input ref="input">
</template>

onWatcherCleanup()

onWatcherCleanup() は、ウォッチャー内でクリーンアップコールバックを登録するために使用されます。このAPIにより、以下のような利点があります:

  1. 古い非同期操作やリソースを確実にクリーンアップできる
  2. コードの整理がしやすくなる
  3. 複雑なクリーンアップシナリオにも対応できる

使用例:

import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const controller = new AbortController()

  fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
    // コールバックロジック
  })

  onWatcherCleanup(() => {
    // 古いリクエストを中止
    controller.abort()
  })
})

3. Deferred Teleport

Vue 3.5では、ビルトインのTeleportコンポーネントに新しいプロパティdeferが導入されました。この機能により、Teleportの動作をより柔軟に制御できるようになりました。

deferプロパティを使用すると、Teleportは同じ更新サイクル内の他のすべてのDOMコンテンツがレンダリングされるまで待機してから、ターゲットコンテナを探して子要素をマウントします。

<Teleport defer to="#container">...</Teleport>
<div id="container"></div>

この例では、<div id="container"><Teleport>の後に配置されていても、正しく解決されます。

deferプロパティは<Suspense>内での使用も可能です:

<Suspense>
  <div>
    <AsyncComp />
    <Teleport defer to="#target">...</Teleport>
    <div id="target"></div>
  </div>
</Suspense>

詳細な実装については、GitHub PR #11387 を参照してください。

https://github.com/vuejs/core/pull/11387

機能改善の詳細

1. Reactivity System の改善

Vue 3.5では、Reactivity Systemに大幅な改善が加えられ、全体的なパフォーマンスと効率性が向上しました。これらの改善は、特に大規模アプリケーションや複雑なデータ構造を扱う場面で効果を発揮します。

主な改善点

  1. アーキテクチャの刷新:

    • バージョンカウンティングの導入: 各リアクティブな値にバージョン番号を割り当て、変更を効率的に追跡します。
    • 双方向リンクリスト構造の採用: 依存関係の追跡がより効率的になり、メモリ使用量を削減しつつ、更新の伝播速度を向上させました。
  2. メモリ使用量の最適化:

    • 56%のメモリ削減: リアクティブなインスタンス(ref、computed、effect)の内部構造を最適化しました。
    • 遅延サブスクリプション: computed プロパティが実際に使用されるまでサブスクリプションを遅延させることで、不要なメモリ消費を抑制しています。
  3. コンピューテッドプロパティの改善:

    • 常に最新の値を保持: 新しい実装により、computed プロパティが停止されたり無効になったりする状況でも、常に正確な値を返すようになりました。
    • SSRでの最適化: グローバルな変更バージョンカウンターを使用することで、SSR中の computed プロパティの再計算を最小限に抑えています。

詳細な実装については、以下のPRをご覧ください:

https://github.com/vuejs/core/pull/10397

https://github.com/vuejs/core/pull/9511

2. SSRの改善

Server-Side Rendering (SSR) は、初期ページロードの高速化とSEO改善に重要な役割を果たします。Vue 3.5では、SSRに関する重要な改善が行われました。

Lazy Hydration

Vue 3.5 では、Lazy Hydration の仕組みが刷新されました。

Lazy Hydration について

Lazy Hydration は、コンポーネントのハイドレーション(クライアントサイドでの動的な機能の復元)を遅延させる機能です。これにより、初期ページロードのパフォーマンスを向上させることができます。

ウェブサイトを訪れる際、ページの内容が表示されるだけでなく、ボタンのクリックやフォームの入力など、さまざまな操作ができるようになります。

この「動きのある」状態にするプロセスがハイドレーションです。Lazy Hydration は、このプロセスを必要なタイミングまで待たせることで、ページの読み込みを高速化する技術です。

より詳細な説明は以下の記事をご覧ください:

https://vueschool.io/articles/vuejs-tutorials/lazy-hydration-and-server-components-in-nuxt-vue-js-3-performance/

Vue 3.5 では、以下の Lazy Hydration 戦略が導入されました:

  1. Hydrate on Idle (アイドル時にハイドレーション)
  2. Hydrate on Visible (表示時にハイドレーション)
  3. Hydrate on Media Query (メディアクエリ一致時にハイドレーション)
  4. Hydrate on Interaction (インタラクション時にハイドレーション)
  5. Custom Strategy (カスタム戦略)

各戦略の詳細な使用例:

import { defineAsyncComponent, hydrateOnIdle, hydrateOnVisible, hydrateOnMediaQuery, hydrateOnInteraction } from 'vue'

// Hydrate on Idle
const IdleComp = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  hydrate: hydrateOnIdle(5000) // 最大5秒待機
})

// Hydrate on Visible
const VisibleComp = defineAsyncComponent({
  loader: () => import('./LazyLoadedComponent.vue'),
  hydrate: hydrateOnVisible({ rootMargin: '200px' })
})

// Hydrate on Media Query
const MobileComp = defineAsyncComponent({
  loader: () => import('./MobileComponent.vue'),
  hydrate: hydrateOnMediaQuery('(max-width: 768px)')
})

// Hydrate on Interaction
const InteractiveComp = defineAsyncComponent({
  loader: () => import('./InteractiveComponent.vue'),
  hydrate: hydrateOnInteraction(['click', 'mouseover'])
})

// Custom Strategy
import { type HydrationStrategy } from 'vue'

const customStrategy: HydrationStrategy = (hydrate, forEachElement) => {
  let shouldHydrate = false
  forEachElement(el => {
    // カスタムロジックを実装
    if (someCondition(el)) {
      shouldHydrate = true
    }
  })
  if (shouldHydrate) {
    hydrate()
  }
  return () => {
    // 必要に応じてクリーンアップロジックを実装
  }
}

const CustomComp = defineAsyncComponent({
  loader: () => import('./CustomComponent.vue'),
  hydrate: customStrategy
})

これらの戦略を適切に使用することで、アプリケーションのパフォーマンスを最適化し、必要なときにのみコンポーネントをハイドレーションすることができます。

data-allow-mismatch

data-allow-mismatch 属性は、サーバーサイドレンダリング(SSR)とクライアントサイドのハイドレーションの間に発生する可能性のある不一致警告を抑制します。

使用例:

<span data-allow-mismatch>{{ data.toLocaleString() }}</span>

また、この属性に値を指定することで、許可する不一致のタイプを制限することもできます:

  1. text: テキストコンテンツの不一致を許可
  2. children: 子コンテンツの不一致を許可
  3. class: クラスの不一致を許可
  4. style: スタイルの不一致を許可
  5. attribute: 属性の不一致を許可

3. Custom Elements の改良

Vue 3.5 では、カスタム要素(Custom Elements)の機能が大幅に改善されました。

主な改善点

  1. アプリケーション設定の柔軟性向上

    • configureApp オプションにより、カスタム要素内で Vue アプリケーションの詳細な設定が可能になりました。
  2. 新 API の追加

    • useHost(): ホスト要素(カスタム要素自体)にアクセスするための関数
    • useShadowRoot(): シャドウルートにアクセスするための関数
    • this.$host: コンポーネント内でホスト要素にアクセスするためのプロパティ
  3. shadowRoot オプションの追加

    • shadowRoot: false オプションを使用することで、シャドウ DOM を使用せずにカスタム要素をマウントできるようになりました。
  4. セキュリティ強化:nonce サポート

    • nonce オプションの導入により、カスタム要素によって注入される <style> タグに nonce を付与できるようになりました。

使用例

import MyElement from './MyElement.ce.vue'

defineCustomElement(MyElement, {
  shadowRoot: false,  // シャドウDOMを使用しない
  nonce: 'xxx',
  configureApp(app) {
    app.config.errorHandler = // エラーハンドラーの設定
  }
})

より詳細な説明は以下の記事をご覧ください:

https://zenn.dev/comm_vue_nuxt/articles/improvements-to-custom-elements-in-vue3-5

まとめ

Vue 3.5は、パフォーマンスの向上、開発体験の改善、そしてより柔軟なアプリケーション構築を可能にする重要なアップデートです。主なポイントは以下の通りです:

  1. 新機能の導入

    • Reactive Props Destructure の安定化により、コードの可読性と保守性が向上しました。
    • 新しいAPI(useTemplateRef(), onWatcherCleanup())の追加により、より柔軟で効率的な開発が可能になりました。
    • Deferred Teleport の導入で、より複雑なコンポーネント構造の管理が容易になりました。
  2. 既存機能の改善

    • Reactivity System の改善により、特に大規模アプリケーションでのパフォーマンスが大幅に向上しました。
    • SSRの改善、特にLazy Hydrationの導入により、初期ページロードの最適化が容易になりました。
    • Custom Elementsの改良により、Vueコンポーネントをより広範囲のシナリオで使用できるようになりました。

Vue 3.5は、現代のWeb開発の課題に対応しつつ、Vueの強みである簡潔さと柔軟性を維持しています。このアップデートにより、Vueはより強力で効率的なフレームワークとして、さらなる進化を遂げたと言えるでしょう。

新機能や改善点を活用することで、より高速で保守性の高いアプリケーションの開発が可能になります。特に、Lazy HydrationやReactivity Systemの改善は、大規模アプリケーションの開発者にとって大きな恩恵をもたらすでしょう。

ぜひ、Vue 3.5 を自身のプロジェクトに導入し、これらの新機能や改善点を体験してみてください。

GitHubで編集を提案
Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion