リアクティビティーの基礎
API の参照
このページと、当ガイドの多くの章では、Options API と Composition API で異なる内容が含まれています。現在の設定は Composition API です。左サイドバーの上部にある「API の参照」スイッチで、API スタイルを切り替えることができます。
リアクティブな状態を宣言する
リアクティブなオブジェクトや配列を作るには、reactive()
関数を使用します。
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
リアクティブなオブジェクトは JavaScript プロキシで、通常のオブジェクトと同じように振る舞います。違いは、Vue がリアクティブなオブジェクトのプロパティアクセスと変更を追跡できることです。詳細については、リアクティビティーの探求で Vue のリアクティブシステムの仕組みを説明していますが、このメインガイドを読み終えた後に読むことをお勧めします。
参照: reactive()
の型付け 。
コンポーネントのテンプレートでリアクティブな状態を使うには、下記に示すように、コンポーネントの setup()
関数で宣言し、それを返します:
js
import { reactive } from 'vue'
export default {
// `setup` 関数は、Composition API 専用の特別なフックです。
setup() {
const state = reactive({ count: 0 })
// 状態をテンプレートに公開します
return {
state
}
}
}
template
<div>{{ state.count }}</div>
同様に、リアクティブな状態を変化させる関数を同じスコープで宣言し、状態と並行してメソッドとして公開することができます:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 関数も公開することを忘れないでください。
return {
state,
increment
}
}
}
通常、公開されたメソッドはイベントリスナーとして使用されます。
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
setup()
関数を使って手動で状態やメソッドを公開すると、冗長になることがあります。幸いなことに、これはビルドステップを使用しない場合にのみ必要です。単一ファイルコンポーネント (SFC) を使用する場合は、 <script setup>
を使用することで大幅に簡略化することができます。
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
トップレベルのインポートと <script setup>
で宣言された変数は、同じコンポーネントのテンプレートで自動的に使用できるようになります。
当ページ残りの部分では、Composition API のコード例として主に SFC +
<script setup>
という構文を使用します。
DOM 更新のタイミング
リアクティブな状態を変化させると、DOM は自動的に更新されます。しかし、DOM の更新は同期的に適用されないことに注意する必要があります。その代わりに Vue は、更新サイクルの「next tick」まで更新をバッファリングし、どれだけ状態を変化させても、各コンポーネントは一度だけ更新することを保証しています。
状態変化後の DOM 更新が完了するのを待つため、nextTick() というグローバル API を使用することができます:
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// DOM 更新にアクセスします
})
}
ディープなリアクティビティー
Vue では、デフォルトで状態がリアクティブになっています。つまり、ネストしたオブジェクトや配列を変化させた場合でも、変更が検出されることが期待できます:
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// これらは期待通りに動作します。
obj.nested.count++
obj.arr.push('baz')
}
また、ルートレベルでのみリアクティビティーを追跡する浅いリアクティブオブジェクトを明示的に作成することも可能ですが、これらは一般的に高度な使用例においてのみ必要とされるものとなります。
リアクティブプロキシ vs. 独自
注意すべきは、reactive()
の戻り値が、元のオブジェクトのプロキシであり、元のオブジェクトと等しくないということです:
js
const raw = {}
const proxy = reactive(raw)
// プロキシはオリジナルと同じではありません。
console.log(proxy === raw) // false
プロキシだけがリアクティブとなります。元のオブジェクトを変更しても更新は行われません。したがって、Vue のリアクティブシステムを使用する際のベストプラクティスは、プロキシされた状態のバージョンだけを使用することになります。
プロキシへの一貫したアクセスを保証するために、同じオブジェクトに対して reactive()
を呼ぶと常に同じプロキシを返し、既存のプロキシに対して reactive()
を呼ぶとその同じプロキシも返されます。
js
// calling reactive() on the same object returns the same proxy
console.log(reactive(raw) === proxy) // true
// calling reactive() on a proxy returns itself
console.log(reactive(proxy) === proxy) // true
このルールは、ネストされたオブジェクトにも適用されます。深いリアクティビティーを持つため、リアクティブなオブジェクトの中にあるネストされたオブジェクトもプロキシとなります。
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
reactive()
の制限
reactive()
API には 2 つの制限があります:
オブジェクト型 (オブジェクト、配列、および
Map
やSet
などの コレクション型) に対してのみ機能します。文字列、数値、真偽値などの プリミティブ型 を保持することはできません。Vue のリアクティビティー追跡はプロパティアクセス上で動作するため、リアクティブなオブジェクトへの参照を常に同じに保つ必要があります。つまり、最初の参照へのリアクティブな接続が失われるため、リアクティブなオブジェクトを簡単に「置き換える」ことはできません:
jslet state = reactive({ count: 0 }) // 上記の参照({ count: 0 })は、もはや追跡されていません(リアクティブな接続が失われました!) state = reactive({ count: 1 })
また、リアクティブなオブジェクトのプロパティをローカル変数に代入したり、分割代入したり、そのプロパティを関数に渡したりすると、下記に示すようにリアクティブなつながりが失われることとなります:
jsconst state = reactive({ count: 0 }) // n は切り離されたローカル変数 // を state.count から取得します。 let n = state.count // 元の状態に戻りません。 n++ // count も state.count と切り離されます。 let { count } = state // 元の状態に戻りません。 count++ // この関数が受け取る平文番号と // state.count の変更を追跡することができません。 callSomeFunction(state.count)
ref()
と共に使うリアクティブな変数
Vue は、reactive()
の制限に対処するため、ref()
という関数も提供しており、任意の値の型を保持できるリアクティブな "refs " を作成することができます:
js
import { ref } from 'vue'
const count = ref(0)
ref()
は引数を受け取り、それを .value
プロパティを持つ ref オブジェクトにラップして返します:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
参照: ref()
の型付け 。
リアクティブなオブジェクトのプロパティと同様に、ref の .value
プロパティはリアクティブとなります。また、オブジェクト型を保持する場合、ref は .value
を reactive()
で自動的に変換します。
オブジェクトの値を含む ref は、オブジェクト全体をリアクティブに置き換えることができます:
js
const objectRef = ref({ count: 0 })
// これはリアクティブに動きます。
objectRef.value = { count: 1 }
また、Ref を関数に渡したり、プレーンオブジェクトから分解したりしても、リアクティビティーが失われることはありません。
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// ref を受け取るこの関数は、
// .value を介して値にアクセスする必要がありますが、それは
// リアクティビティーを保持します。
callSomeFunction(obj.foo)
// リアクティビティーを保持しています。
const { foo, bar } = obj
つまり、ref()
を使うと、任意の値への「参照」を作り、リアクティビティーを失わずに受け渡しすることができます。この能力は、ロジックをコンポーザブル関数に抽出する際に頻繁に使用されるため、非常に重要となります。
Ref Unwrapping in Templates
ref がテンプレートのトップレベルのプロパティとしてアクセスされた場合、それらは自動的に「アンラップ」されるので、.value
を使用する必要はありません。以下は、先ほどのカウンターの例で、代わりに ref()
を使用したものとなります:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- .value は必要ありません -->
</button>
</template>
アンラップは、ref がテンプレートに描画されるコンテキスト上のトップレベルのプロパティである場合にのみ適用されることに注意してください。例として object
はトップレベルのプロパティですが、object.foo
はトップレベルではありません。
そのため、下記に示したようなオブジェクトがあるとすると:
js
const object = { foo: ref(1) }
下記に示した式は、期待通りに動作 しません :
template
{{ object.foo + 1 }}
レンダリング結果は [object Object]1
となります。これは object.foo
が ref オブジェクトであるためです。これを解決するには、下記に示すように foo
をトップレベルのプロパティにします:
js
const { foo } = object
template
{{ foo + 1 }}
これで、レンダリング結果は「2」になります。
注意点としては、ref がテキスト補間の最終評価値(つまり {{ }}
タグ)である場合もアンラップされるので、以下のように 1
がレンダリングされます。
template
{{ object.foo }}
これはテキスト補間の便利な機能に過ぎず、 {{ object.foo.value }}
と等価になります。
リアクティブなオブジェクトにおける Ref のアンラッピング
リアクティブなオブジェクトのプロパティとして ref
にアクセスしたり変化させたりすると、自動的にアンラップされるので、通常のプロパティと同じように振る舞うことができます:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
既存の ref にリンクされたプロパティに新しい ref が割り当てられた場合、下記に示すように、それは古い ref を置き換えることとなります:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 元の ref は state.count から切り離されました。
console.log(count.value) // 1
Ref のアンラッピングは、より深いリアクティブなオブジェクトの内部にネストされている場合にのみ発生します。浅いリアクティブなオブジェクト のプロパティとしてアクセスされた場合は適用されません。
配列とコレクションにおける Ref のアンラッピング
リアクティブなオブジェクトと異なり、ref がリアクティブな配列の要素や、Map
のようなネイティブコレクション型としてアクセスされた場合には、アンラップは行われません。
js
const books = reactive([ref('Vue 3 Guide')])
// ここでは .value が必要となります
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// ここでは .value が必要となります
console.log(map.get('count').value)