Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
NaokiHaba opened this issue Sep 16, 2024 · 11 comments
Closed

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

NaokiHaba opened this issue Sep 16, 2024 · 11 comments
Assignees

Comments

@NaokiHaba
Copy link
Owner

NaokiHaba commented Sep 16, 2024

---
title: "Vue 3.5 アップデートについて"
emoji: "🦁"
type: "tech"
topics: ["Vue", "JavaScript", "フロントエンド"]
published: false
---
@NaokiHaba
Copy link
Owner Author

はじめに

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
Copy link
Owner Author

Reactive Props Destructure の安定化

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

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

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

RFC

vuejs/rfcs#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
Copy link
Owner Author

NaokiHaba commented Sep 16, 2024

SSRの改善

Lazy Hydration

vuejs/core#11530

vuejs/core#11458

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

  • requestIdleCallbackを使用して、ブラウザがアイドル状態のときにコンポーネントをハイドレーションします。
  • 使用例:
    import { defineAsyncComponent, hydrateOnIdle } from 'vue'
    
    const AsyncComp = defineAsyncComponent({
      loader: () => import('./HeavyComponent.vue'),
      hydrate: hydrateOnIdle(5000) // 最大5秒待機
    })

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

  • IntersectionObserverを使用して、コンポーネントが画面に表示されたときにハイドレーションします。
  • 使用例:
    import { defineAsyncComponent, hydrateOnVisible } from 'vue'
    
    const AsyncComp = defineAsyncComponent({
      loader: () => import('./LazyLoadedComponent.vue'),
      hydrate: hydrateOnVisible({ rootMargin: '200px' })
    })

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

  • 指定されたメディアクエリが一致したときにコンポーネントをハイドレーションします。
  • 使用例:
    import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'
    
    const MobileComp = defineAsyncComponent({
      loader: () => import('./MobileComponent.vue'),
      hydrate: hydrateOnMediaQuery('(max-width: 768px)')
    })
  • 注意点: メディアクエリの変更が頻繁に起こる場合、パフォーマンスに影響を与える可能性があります。

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

  • 指定されたイベント(クリックなど)が発生したときにコンポーネントをハイドレーションします。
  • ハイドレーション完了後、トリガーとなったイベントが再度実行されます。
  • 使用例:
    import { defineAsyncComponent, hydrateOnInteraction } from 'vue'
    
    const InteractiveComp = defineAsyncComponent({
      loader: () => import('./InteractiveComponent.vue'),
      hydrate: hydrateOnInteraction(['click', 'mouseover'])
    })

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

  • 独自のハイドレーション戦略を定義できます。
  • forEachElementヘルパー関数を使用して、コンポーネントのルート要素にアクセスできます。
  • 使用例:
    import { defineAsyncComponent, 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
    })

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

@NaokiHaba NaokiHaba self-assigned this Sep 16, 2024
@NaokiHaba
Copy link
Owner Author

NaokiHaba commented Sep 16, 2024

useId()

vuejs/core#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
Copy link
Owner Author

data-allow-mismatch

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

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

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

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

@NaokiHaba
Copy link
Owner Author

NaokiHaba commented Sep 16, 2024

Custom Elements の改良

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

主な改善点

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

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

  1. 新APIの追加

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

  • useHost(): ホスト要素(カスタム要素自体)にアクセスするための関数
  • useShadowRoot(): シャドウルートにアクセスするための関数。シャドウDOM内の要素操作やスタイリングに有用です。
  • this.$host: コンポーネント内でホスト要素にアクセスするためのプロパティ

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

  1. shadowRoot オプションの追加

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

  • シャドウDOMのカプセル化が不要な場合
  • 既存のCSSとの互換性を維持したい場合
  • グローバルスタイルを直接カスタム要素に適用したい場合

この機能により、開発者はシャドウ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
Copy link
Owner Author

その他の機能追加

@NaokiHaba
Copy link
Owner Author

NaokiHaba commented Sep 16, 2024

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
Copy link
Owner Author

NaokiHaba commented Sep 16, 2024

Deferred Teleport

@NaokiHaba
Copy link
Owner Author

onWatcherCleanup()

@NaokiHaba
Copy link
Owner Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant