浅いリアクティビティAPI
shallowRef
shallowRefは、値そのものへの変更のみを追跡し、その値がオブジェクトや配列であっても、その内部プロパティへの変更は追跡しないリアクティブな参照を作成します。
基本的な使い方
import { shallowRef } from 'vue'
const shallowState = shallowRef({ count: 0 })
この例では、shallowState.value全体を新しいオブジェクトに置き換えた場合のみ、UIが更新されます。shallowState.value.count++のように内部のプロパティを変更しても、更新はトリガーされません。
実装例
<template>
<div>
<p>カウンター値: {{ counter.count }}</p>
<p>名前: {{ userProfile.name }}</p>
<button @click="increment">カウント増加(無効)</button>
<button @click="replaceProfile">プロフィール全体を更新</button>
</div>
</template>
<script setup>
import { shallowRef } from 'vue'
const counter = shallowRef({ count: 0 })
const userProfile = shallowRef({ name: '山田太郎' })
function increment() {
// これはUIを更新しない
counter.value.count++
}
function replaceProfile() {
// これはUIを更新する
userProfile.value = { name: '鈴木一郎' }
}
</script>
shallowReactive
shallowReactiveは、オブジェクトの最上位プロパティのみをリアクティブにします。ネストされたオブジェクトのプロパティはリアクティブにはなりません。
基本的な使い方
import { shallowReactive } from 'vue'
const shallowObj = shallowReactive({
id: 1,
nested: { value: 'a' }
})
shallowObj.id++はUIを更新しますが、shallowObj.nested.value = 'b'は更新しません。これにより、大規模なオブジェクト構造のパフォーマンスが向上する場合があります。
読み取り専用API
readonly
readonlyは、オブジェクト(またはref)の読み取り専用プロキシを作成します。ネストされたすべてのプロパティも読み取り専用になります。元のオブジェクトが変更されると、読み取り専用プロキシも反映されますが、プロキシ自体を直接変更しようとすると警告が発生します。
実装例
<template>
<div>
<p>元のカウント: {{ originalCount }}</p>
<p>読み取り専用カウント: {{ readOnlyCount }}</p>
<button @click="updateOriginal">元の値を更新</button>
<button @click="updateReadOnly">読み取り専用値を更新(エラー)</button>
</div>
</template>
<script setup>
import { ref, readonly } from 'vue'
const originalCount = ref(0)
const readOnlyCount = readonly(originalCount)
function updateOriginal() {
originalCount.value++
}
function updateReadOnly() {
// 開発モードでコンソールに警告が表示される
readOnlyCount.value++
}
</script>
shallowReadonly
shallowReadonlyは、オブジェクトの最上位プロパティを読み取り専用にしますが、ネストされたオブジェクトは変更可能なままです。
生のオブジェクト操作API
toRaw
toRawは、reactiveまたはreadonlyで作成されたプロキシの元となる、生のオブジェクトを取得します。この生のオブジェクトへの変更は、トリガーや追跡のコストなしに行われますが、UIは更新されません。主に、サードパーティのライブラリに生のオブジェクトを渡す際に使用します。
markRaw
markRawは、オブジェクトがリアクティブに変換されることを永久に阻止します。これにより、そのオブジェクトがreactiveのソースとして使用されても、リアクティブなプロキシにはなりません。複雑な静的データ構造や、サードパーティ製のクラスインスタンスなど、不変であるべきものをマークするのに役立ちます。
実装例
import { reactive, markRaw, isReactive } from 'vue'
const staticConfig = markRaw({
apiEndpoint: 'https://api.example.com',
version: '1.0.0'
})
// staticConfigはリアクティブにならない
const state = reactive({ config: staticConfig })
console.log(isReactive(staticConfig)) // false
console.log(isReactive(state.config)) // false
カスタムRef
customRefは、依存関係の追跴(track)と更新のトリガー(trigger)を明示的に制御する、独自のrefロジックを実装するための関数です。デバウンスやスロットリングなどの高度な機能を実現するのに適しています。
デバウンス機能を持つカスタムRefの例
useDebouncedRef.js
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeoutId
return customRef((track, trigger) => {
return {
get() {
track() // Vueにこの値が依存関係であることを伝える
return value
},
set(newValue) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
value = newValue
trigger() // Vueに再レンダリングを促す
}, delay)
}
}
})
}
Component.vue
<template>
<div>
<input v-model="searchQuery" />
<p>検索クエリ: {{ searchQuery }}</p>
</div>
</template>
<script setup>
import { useDebouncedRef } from './useDebouncedRef'
const searchQuery = useDebouncedRef('初期値', 500)
</script>
Vue 3の新しい組み込みコンポーネント
Teleport
Teleportは、コンポーネントのテンプレートの一部を、DOMツリー内の完全に別の場所(例えば、<body>タグの直下など)にレンダリングすることを可能にします。モーダルダイアログや通知など、CSSのz-indexやoverflowの問題から解放されるために非常に便利です。
実装例
<template>
<div>
<h3>メインコンテンツ</h3>
<button @click="isDialogVisible = true">ダイアログを開く</button>
<teleport to="body">
<div v-if="isDialogVisible" class="modal-overlay">
<div class="modal-dialog">
<p>これはダイアログです。</p>
<button @click="isDialogVisible = false">閉じる</button>
</div>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isDialogVisible = ref(false)
</script>
Suspense
Suspenseは、非同期コンポーネント(例えば、<script setup>内でasync/awaitを使用するコンポーネント)のロードを待機している間、フォールバックコンテンツ(ローディングインジケータなど)を表示するための仕組みです。
実装例
AsyncComponent.vue
<template>
<div>
<h3>非同期コンポーネント</h3>
<p>データ: {{ data.message }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const { data } = await fetch('https://api.example.com/message').then(res => res.json())
</script>
App.vue
<template>
<div>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<p>読み込み中...</p>
</template>
</Suspense>
</div>
</template>
<script setup>
import AsyncComponent from './AsyncComponent.vue'
</script>
グローバルAPIのアプリケーションインスタンスへの移行
Vue 2ではVue.からアクセスしていた多くのグローバルAPIは、Vue 3ではアプリケーションインスタンス(createAppが返すオブジェクト)を介して使用するように変更されました。
- コンポーネント登録:
Vue.component→app.component - グローバルプロパティ:
Vue.prototype→app.config.globalProperties - ディレクティブ登録:
Vue.directive→app.directive - プラグイン使用:
Vue.use→app.use - マウント:
new Vue({}).$mount→app.mount - アンマウント:
app.unmount(新規追加)
その他の主な非互換な変更点
- トランジションクラス名の変更:
v-enter→v-enter-from,v-leave→v-leave-from。 - keyCodeの非推奨:
v-onでのキーコード修飾子はサポートされなくなりました。 - v-modelの変更: コンポーネントでの
v-modelの動作が変更され、v-bind.syncは廃止されました。 - v-ifとv-forの優先順位:
v-ifがv-forより優先されるようになりました。併用は非推奨です。 - イベントインスタンスメソッドの削除:
$on,$off,$onceが削除されました。 - フィルタの削除: フィルタは削除され、代わりにメソッドまたは算出プロパティの使用が推奨されます。
- $childrenの削除:
$childrenインスタンスプロパティが削除されました。