404 Not Found

404 Not Found


nginx

Vue 3.4 以降の新しい機能:`defineModel`、リアクティブなデストラクチャリング、および `useTemplateRef` を詳しく解説

Vue 3.4+(2023年12月リリース)では、いくつかの画期的な改善が導入されました。具体的には、defineModel マクロの簡素化 v-model、リアクティブなプロパティの展開 useTemplateRef、Composition API、そして useId 一意のID生成機能です。これらの機能により、テンプレートコードが大幅に簡素化され、開発体験が向上します。

このレッスンでは、Vue 3 のコードをより簡潔でモダンなものにする 5 つの重要な新機能を習得できます。

1. 学習内容


2. v-model コンポーネントにおける「親子同期」の課題

(1) 課題:propsが5行+emitが5行=計10行の定型コード

Aliceは、v-model を使用してカスタム入力コンポーネントを作成しました:

JS
// ❌ The "Flip" Version:v-model Required 10 Sample Code
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])

const internalValue = ref(props.modelValue)
watch(() => props.modelValue, (val) => {
  internalValue.value = val
})
watch(internalValue, (val) => {
  emit('update:modelValue', val)
})

10行の定型コード――v-modelコンポーネントごとにこれを記述する必要があります

主任開発者:

「アリス、これは冗長すぎるよ。Vue 3.4には、これを1行で実現できるdefineModelがあるんだ。」

(2) 図3.4:defineModelマクロの一般解

JS
// ✅ Correct Version:1 All set v-model
const modelValue = defineModel('modelValue')

// It's possible now:
// - Read: modelValue.value
// - Write: modelValue.value = 'new value' (Automatic emit)
// - watch:watch(modelValue, ...)
// - Parent Component v-model Two-way binding works as usual

10行 → 1行。Vモデルの5つの構成要素により、50行のコードが削減されました。

(3) 収益

defineModel にアップグレードした後:


3. defineModel マクロの詳細な説明

(1) 基本的な使い方

VUE
<!-- Child component:CustomInput.vue -->
<template>
  <input :value="modelValue" @input="modelValue = $event.target.value">
</template>

<script setup>
// ✅ 1-line v-model
const modelValue = defineModel('modelValue')
</script>
VUE
<!-- Parent Component:Usage v-model -->
<template>
  <CustomInput v-model="searchQuery" />
  <p>Search: {{ searchQuery }}</p>
</template>

<script setup>
import { ref } from 'vue'
const searchQuery = ref('')
</script>

(2) 5つの応用例

JS
// 1. Default value
const modelValue = defineModel('modelValue', { default: '' })

// 2. Type Constraints(TypeScript)
const count = defineModel<number>('count', { default: 0 })

// 3. transform Convert(Conversion at the Time of Writing)
const trimmed = defineModel('text', {
  set(value) {
    return value.trim()  // Automatic trim
  }
})

// 4. getter(Conversion During Reading)
const upper = defineModel('text', {
  get(value) {
    return value.toUpperCase()  // Always uppercase
  }
})

// 5. Several v-model
const name = defineModel('name')
const email = defineModel('email')
const phone = defineModel('phone')

(3) 完全な例

VUE
<!-- Child component:TrimInput.vue -->
<template>
  <input :value="value" @input="value = $event.target.value">
</template>

<script setup>
const value = defineModel('value', {
  // get: Conversion During Reading
  get(val) {
    return val?.toUpperCase() || ''
  },
  // set: Conversion at the Time of Writing
  set(val) {
    return val?.trim() || ''
  },
  default: ''
})
</script>
VUE
<!-- Parent Component -->
<CustomInput v-model="name" />
<!-- User Input "alice" → trim Unchanged → uppercase → "ALICE" -->

4. リアクティブプロパティのデストラクチャリング

(1) Vue 3.4 以前の課題点

JS
// ❌ Vue 3.3 and before that
const props = defineProps({ count: Number, name: String })

// Use directly in the template props.count
// JS Key Points props.count
// Deconstruction may result in a loss of response
const { count, name } = props  // count/name Convert to a regular value

(2) Vue 3.4:レスポンシブ性を維持しながらのデストラクチャリング

VUE
<!-- Child component -->
<template>
  <p>{{ count }} - {{ name }}</p>
  <button @click="count++">+1</button>
</template>

<script setup>
// ✅ Vue 3.4:Remains responsive even after deconstruction
const { count, name } = defineProps<{
  count: number
  name: string
}>()
</script>

分解されたプロパティは引き続き反応性を維持します!これはVue 3.4における最大の改善点の1つです。

(3) 5つの主な活用シナリオ

JS
// 1. Simplify props Usage
const { count, name } = defineProps<{ count: number; name: string }>()

// 2. Deconstruction + Rename
const { count: total, name: userName } = defineProps<{ count: number; name: string }>()

// 3. Deconstruction + Default value
const { count = 0, name = 'Guest' } = defineProps<{ count?: number; name?: string }>()

// 4. In conjunction with watch(After deconstruction watch Still working)
const { count } = defineProps<{ count: number }>()
watch(count, (newVal) => console.log('count changed:', newVal))

// 5. In conjunction with computed(Stay responsive as well)
const { count } = defineProps<{ count: number }>()
const double = computed(() => count * 2)

(4) 重要な注意事項

JS
// ⚠️ Deconstructed props Cannot be reassigned
const { count } = defineProps<{ count: number }>()
count = 10  // ❌ Error:Assignment to constant variable

// ✅ Correct:Via emit Have the parent component modify
const emit = defineEmits(['update:count'])
const handleClick = () => emit('update:count', count + 1)

5. useId 一意のID

(1) なぜ useId が必要なのでしょうか?

SSR(サーバーサイドレンダリング)では、手動で記述されたIDによって競合(複数のコンポーネントインスタンスが同じIDを使用する)が発生する可能性があります。useIdは、グローバルに一意で、SSRに対応したIDを生成します。

JS
// ❌ Old notation:Handwritten ID,SSR There will be a conflict
const id = `input-${Math.random()}`

// ✅ Vue 3.4+:useId
import { useId } from 'vue'
const id = useId()  // 'v-0', 'v-1', ...

(2) 5つの主なユースケース

VUE
<!-- 1. Form label Relationship -->
<template>
  <label :for="id">Username</label>
  <input :id="id" v-model="username">
</template>

<script setup>
import { useId } from 'vue'
const id = useId()
const username = ref('')
</script>

<!-- 2. ARIA Properties -->
<template>
  <button :aria-describedby="`${id}-help`">Click</button>
  <p :id="`${id}-help`">This button does X</p>
</template>

<!-- 3. Form Error Messages -->
<template>
  <input v-model="email" :aria-invalid="!!error" :aria-errormessage="`${id}-error`">
  <p v-if="error" :id="`${id}-error`">{{ error }}</p>
</template>

<!-- 4. Tooltip Relationship -->
<template>
  <button :aria-describedby="`${id}-tip`">?</button>
  <span :id="`${id}-tip`" class="tooltip">Help text</span>
</template>

<!-- 5. Modal Window -->
<template>
  <div :id="`${id}-modal`" role="dialog" aria-modal="true">
    ...
  </div>
</template>

(3) 5つの主な利点

利点 説明
SSRセキュリティ サーバー/クライアントIDの一致
一意 同じページ内に複数のインスタンスがあっても競合しない
安定版 コンポーネントの再レンダリングは変更なし
読みやすい 識別を容易にするため、IDには v- というプレフィックスが付いています
軽量 依存関係 0 つ(Vue に組み込まれている)

6. useTemplateRef(Vue 3.5+ の詳細な解説)

(1) すべての機能

VUE
<template>
  <input ref="usernameInput">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'
import MyChart from './MyChart.vue'

// ✅ Vue 3.5+:One Name Does It All
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

// ✅ TypeScript Perfect Inference
onMounted(() => {
  inputRef.value?.focus()  // HTMLInputElement Type
  chartRef.value?.refresh()  // Component Instance Methods
})
</script>

(2) Vue 3.5 と Vue 3.4 の比較

ディメンション Vue 3.4 の ref 変数 Vue 3.5 の useTemplateRef
テンプレート参照 <input ref="inputRef"> <input ref="usernameInput">
JS 参照 const inputRef = ref(null) const inputRef = useTemplateRef('usernameInput')
型推論 手動 ref<HTMLInputElement | null> 自動推論
名称の一貫性 誤記の可能性があってもフラグは立てられない 一貫性がない場合はフラグが立てられる
簡潔さ 2行 1行

7. 5 推奨されるアップグレードパス

(1) アップグレードの時期

Vueのバージョン リリース日 ステータス
Vue 3.0 2020年9月 旧バージョン(アップグレード推奨)
Vue 3.2 2021年8月 安定版
Vue 3.3 2023年5月 安定版
Vue 3.4 2023年12月 現在推奨
Vue 3.5 2024年9月 最新の安定版

(2) アップグレード一覧

TEXT
✅ Vue 3.4:
  - defineModel Macro Replacement v-model Complex Notation
  - Responsive props Deconstruction
  - Abbreviations with the Same Name(<input :value> Replace :value="value")
  - Improvements to Error Handling
  - Template Parser Override(2x Performance)

✅ Vue 3.5:
  - useTemplateRef(Recommendations)
  - Responsive props Deconstruction and Improvement
  - useId Stable
 - Deferred teleport
  - Improved Suspense

(3) アップグレードに関する推奨事項

シナリオ 推奨事項
新規プロジェクト Vue 3.5 以降を直接使用
レガシープロジェクト (Vue 3.3–) 3.5 へのアップグレード、機能ごとの移行
レガシープロジェクト(Vue 3.0–3.2) 3.3へ、その後3.5へアップグレード
メガプロジェクト 段階的なアップグレード(まず Vue をアップグレードし、その後 v-model を移行するなど)

8. 完全な例:Vue 3.4+ を用いた包括的なアプリケーション

(1) ▶ サンプル:. defineModelの5つの活用法

VUE
<!-- 1. Basics -->
<script setup>
const modelValue = defineModel('modelValue')
</script>

<!-- 2. Default value -->
<script setup>
const count = defineModel('count', { default: 0 })
</script>

<!-- 3. TypeScript Type -->
<script setup lang="ts">
const user = defineModel<User>('user', { default: () => ({}) })
</script>

<!-- 4. transform Convert -->
<script setup>
const trimmed = defineModel('text', {
  set(val) { return val?.trim() || '' }
})
</script>

<!-- 5. Several v-model -->
<script setup>
const username = defineModel('username')
const password = defineModel('password')
</script>
▶ 試してみよう

(2) ▶ サンプル:. リアクティブプロパティのデストラクチャリング

VUE
<template>
  <p>{{ count }} - {{ name }}</p>
  <button @click="emit('update:count', count + 1)">+1</button>
</template>

<script setup lang="ts">
// ✅ Vue 3.4+:Deconstructing Responsiveness
const { count, name } = defineProps<{
  count: number
  name: string
}>()

const emit = defineEmits<{
  'update:count': [value: number]
}>()
</script>
▶ 試してみよう

(3) ▶ サンプル:. useId 5 の主なユースケース

VUE
<template>
  <label :for="id">Email</label>
  <input :id="id" v-model="email" :aria-invalid="!!error" :aria-errormessage="`${id}-error`">
  <p v-if="error" :id="`${id}-error`" class="error">{{ error }}</p>
</template>

<script setup>
import { ref, useId } from 'vue'
const id = useId()  // v-0, v-1, ...
const email = ref('')
const error = ref('')
</script>
▶ 試してみよう

(4) ▶ サンプル:. useTemplateRef を使用して申請を完了する

VUE
<template>
  <input ref="usernameInput" v-model="username">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

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

const username = ref('')
const chartData = ref([1, 2, 3])

// ✅ Vue 3.5+:TypeScript Perfect Inference
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

onMounted(() => {
  inputRef.value?.focus()
  chartRef.value?.refresh()
})
</script>
▶ 試してみよう

(5) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス

エラー 症状 解決策
defineModel の分解 応答なし const modelValue = defineModel() を直接使用
props のデストラクチャリング後の代入 エラー emit を使用して親が更新できるようにしてください
ハードコーディングされた文字列を使用した useId SSR との競合 useId() への切り替え
useTemplateRef の名前が不正です ref.value が null です 名前が一致している必要があります
Vue 3.4 以降へのアップグレードには変更点が多すぎる 互換性の問題 段階的な移行

(6) ▶ サンプル:. Vue 3.4 以降へのアップグレードによる 5 つの主なメリット

収益 データ
コード量の削減 30~50%(defineModelの簡素化)
パフォーマンスの向上 テンプレートの解析速度が2倍に
型推論 100% 正確 (TypeScript)
SSR対応 useIdによるIDの競合解消
開発者体験 DXの大幅な改善

❓ よくある質問

Q defineModelv-model の構文全体を置き換えるものですか?
A はい。defineModel('modelValue') is equivalent to the 10 lines of code in props.modelValue + emit('update:modelValue') です。完全な下位互換性があります。
Q リアクティブなプロップのデストラクチャリングはいつサポートされるようになりますか?
A Vue 3.4以降(2023年12月)。Vue 3.3およびそれ以前のバージョンではサポートされていません。デストラクチャリングされたプロップもリアクティブであり続けます(デフォルトではディープリアクティビティが有効です)。
Q useId はレンダリングのたびに変わりますか?
A いいえ。useId によって生成される ID は、コンポーネントのライフサイクルを通じて一定です(SSR とクライアント側で一貫しています)。
Q useTemplateRef と ref 変数の違いは?
A useTemplateRef は Vue 3.5 以降で導入されました。これにより、TypeScript による型推論が強化され、変数名の変更もより安全に行えるようになりました。一般的なプロジェクトでは、ref 変数で十分です。
Q defineProps のデストラクチャリングは watch と併用できますか?
A はい。const { count } = defineProps() + watch(count, ...) は問題なく動作します。
Q Vue 3.3 から 3.4 以降へアップグレードするにはどうすればよいですか?
A npm update vue@latest。コードレベルでは、複雑な v-model 構文の代わりに defineModel を使用し、props.xxx の代わりにリアクティブなデストラクチャリングを使用します。互換性の問題は一切ありません。
Q Vue 3.5以降では、TypeScriptに関してどのような改善がなされましたか?
A definePropsによるリアクティブなデストラクチャリング、useTemplateRefによる完全な型推論、そしてマクロに対するより包括的なTypeScriptサポートが追加されました。<script setup lang="ts">の使用をお勧めします。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

defineModel を使用して v-model コンポーネントをリファクタリングする:

(2) 上級問題(難易度:⭐⭐)

Vue 3.4 以降でリアクティブ・デストラクチャリングを使用してコンポーネントを書き換える方法:

(3) チャレンジ問題(難易度:⭐⭐⭐)

Vue 3.4+ への完全なアップグレードの例:

  1. 5つのコンポーネントにおいて、複雑なVモデルの構文をdefineModelに置き換える
  2. リアクティブプロップを使用して3つのコンポーネントを分解する
  3. useId を使用した 5 つのコンポーネント(labelinput を接続するため)
  4. useTemplateRef を使用した 3 つのコンポーネント(Vue 3.5 以上)
  5. 5 TypeScriptの厳密型付けコンポーネント
  6. SSRシナリオにおけるuseIdの一貫性を検証する
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%