NaokiHaba / zenn-content

My Zenn articles
https://zenn.dev/naonao70
0 stars 1 forks source link

Vue 3.5 アップデートについて #4

Closed NaokiHaba closed 1 month ago

NaokiHaba commented 1 month ago
---
title: "Vue 3.5 アップデートについて"
emoji: "🦁"
type: "tech"
topics: ["Vue", "JavaScript", "フロントエンド"]
published: false
---
NaokiHaba commented 1 month ago

はじめに

2024年9月1日に Vue 3.5 がリリースされました。

この記事では、Vue 3.5 の主な機能改善・追加とその背景について解説します。

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

主な機能改善・追加

  1. Reactive Props Destructure の安定化
  2. SSRの改善
    • Lazy Hydration
    • useId()
    • data-allow-mismatch
  3. Custom Elements Improvements の改良
  4. その他の機能追加
    • useTemplateRef()
    • Deferred Teleport
    • onWatcherCleanup()
  5. Reactivity System の改善によるパフォーマンス向上
NaokiHaba commented 1 month ago

Reactive Props Destructure の安定化

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

より詳細な内部実装を知りたい方は以下の記事を参照してください。

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

RFC

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

使用例

以前の書き方:

<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 を使用せずにデフォルト値を設定できるようになりました。また、プロパティへのアクセスが直接的になり、コードの可読性が向上しています。

内部的な動作の変化

Vue 3.5の新機能により、コンパイル後のコードにも変更が加えられています。以下に、コンパイル前後のコードの違いを示します。

Reactive Props Destructure 以前

入力:

<script setup lang="ts">
import { computed } from 'vue'

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

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

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

出力(一部抜粋):

const __sfc__ = /*#__PURE__*/_defineComponent({
  // ...
  setup(__props, { expose: __expose }) {
    __expose();

    const props = __props

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

    const __returned__ = { props, double }
    // ...
    return __returned__
  }
});

Reactive Props Destructure 以降

入力:

<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>

出力(一部抜粋):

const __sfc__ = /*#__PURE__*/_defineComponent({
  // ...
  props: {
    count: { type: Number, required: false, default: 0 }
  },
  setup(__props, { expose: __expose }) {
    __expose();

    const double = computed(() => __props.count * 2)

    const __returned__ = { double }
    // ...
    return __returned__
  }
});

この変更により、プロパティのデフォルト値がコンポーネントの props オプション内で直接定義されるようになり、setup 関数内でのプロパティの取り扱いが簡略化されています。

ただし、RFCで言及されている通り、props と通常の変数を視覚的に区別するのが難しくなっているため、@vue/language-tools 2.1 以降では、オプトイン設定でインレイヒント(inlay hints)を有効にできるようになりました。

<script setup lang="ts">
import { defineProps } from 'vue'

const { count = 0, msg = 'hello' } = defineProps<{
  count?: number
  message?: string
}>()

// props.count と props.msg の両方がインレイヒントでハイライトされる
console.log(count, msg)
</script>
NaokiHaba commented 1 month ago

SSRの改善

Lazy Hydration

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

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

1. Hydrate on Idle (アイドル時にハイドレーション)

2. Hydrate on Visible (表示時にハイドレーション)

3. Hydrate on Media Query (メディアクエリ一致時にハイドレーション)

4. Hydrate on Interaction (インタラクション時にハイドレーション)

5. Custom Strategy (カスタム戦略)

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

NaokiHaba commented 1 month ago

useId()

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

useId() は、Vue 3.5で導入された新しいコンポジションAPIで、Reactの useId と類似した機能を提供します。このAPIは、フォーム要素やアクセシビリティ属性に使用できるユニークなIDを生成します。

入力:

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

const id = useId()
</script>

<template>
  <form>
    <label :for="id">Name:</label>
    <!-- <input id="v:0" type="text"> -->
    <input :id="id" type="text" />
  </form>
</template>

出力(一部抜粋):

<script setup> で 呼び出した useId の値が、render 関数内で id として使用されていることがわかります。

/* Analyzed bindings: {
  "useId": "setup-const",
  "id": "setup-maybe-ref"
} */
setup(__props, { expose: __expose }) {
  __expose();
  const id = useId()
  const __returned__ = { id, useId }
  Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
  return __returned__
}

function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("form", null, [
    _createElementVNode("label", { for: $setup.id }, "Name:", 8 /* PROPS */, _hoisted_1),
    _createElementVNode("input", {
      id: $setup.id,
      type: "text"
    }, null, 8 /* PROPS */, _hoisted_2)
  ]))
}
NaokiHaba commented 1 month ago

data-allow-mismatch

サーバーサイドレンダリング(SSR)とクライアントサイドのハイドレーションの間に発生する可能性のある不一致警告を抑制できる

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

また、この属性に値を指定することで、許可する不一致のタイプを制限することもできます。指定可能な値は以下の通りです:

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

Custom Elements の改良

Vue 3.5 では、カスタム要素(Custom Elements)の機能が大幅に改善されました。これらの改良により、開発者はより柔軟にカスタム要素を設定し、制御できるようになりました。

主な改善点

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

新たに導入されたconfigureAppオプションにより、カスタム要素内でVueアプリケーションの詳細な設定が可能になりました。これにより、エラーハンドリングなどのアプリケーションレベルの設定をカスタム要素に適用できます。

  1. 新APIの追加

カスタム要素とその環境へのアクセスを容易にする新しいAPIが追加されました:

これらの新APIにより、開発者はシャドウDOMとホスト要素をより効果的に操作できるようになりました。

  1. shadowRoot オプションの追加

shadowRoot: falseオプションを使用することで、シャドウDOMを使用せずにカスタム要素をマウントできるようになりました。これにより、以下のような状況での柔軟性が向上します:

この機能により、開発者はシャドウDOMの利点とグローバルスコープの柔軟性のバランスを取ることができます。シャドウDOMを使用しない場合、カスタム要素の内部構造は通常のDOM構造として扱われ、外部からのアクセスやスタイリングが可能になります。

  1. セキュリティ強化:nonceサポート

nonceオプションの導入により、カスタム要素によって注入される<style>タグにnonceを付与できるようになりました。これにより、Content Security Policy (CSP)の実装が容易になり、全体的なセキュリティが向上します。

nonceは、スクリプトやスタイルシートの実行を制御するためのセキュリティ機能で、特定のリソースが信頼できるソースからのものであることを確認するのに役立ちます。

使用例

以下は、これらの新機能を活用したカスタム要素の定義例です:

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

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

この例では、シャドウDOMを使用せず、nonceを設定し、アプリケーションレベルのエラーハンドラーを構成しています。シャドウDOMを使用する場合は、shadowRoot: falseを省略するか、trueに設定します。

これらの改善により、Vue 3.4でのカスタム要素の使用がより柔軟かつ強力になり、さまざまなユースケースに対応できるようになりました。開発者は、シャドウDOMの利点を活かしつつ、必要に応じてグローバルスコープとの連携も選択できるようになりました。

NaokiHaba commented 1 month ago

その他の機能追加

NaokiHaba commented 1 month ago

useTemplateRef()

useTemplateRef() を使ってテンプレート参照を取得する新しい方法が導入されました。

3.5以前では、静的な ref 属性と一致する変数名を持つプレーンな参照(ref)を使用することを推奨していました。

この古い方法では、ref 属性がコンパイラによって解析可能である必要があったため、静的な ref 属性に限定されていました。

対照的に、useTemplateRef() は実行時の文字列 ID を介して参照をマッチングするため、動的に変更される ID への ref バインディングをサポートしています。

新しい方法:

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

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

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

以前の書き方:

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const inputRef = ref(null)

onMounted(() => {
  // inputRef.value は対応する DOM 要素を参照します
  console.log(inputRef.value)
})
</script>

<template>
  <input ref="inputRef">
</template>
NaokiHaba commented 1 month ago

Deferred Teleport

NaokiHaba commented 1 month ago

onWatcherCleanup()

NaokiHaba commented 1 month ago

Reactivity System の改善によるパフォーマンス向上