はじめに
VueのComposition APIで状態を管理するとき、refやreactiveを使います。
例えば、次のようなコードです。
const count = ref(0)
const user = reactive({
name: 'Taro'
})
私は普段の実装では、基本的にrefを使っています。
const count = ref(0)
const user = ref({
name: 'Taro'
})
どちらを使えばいいのか、という話はよく見聞きしますが、
正直なところ 「refはreactiveが使えない場面でも使えるらしいから、とりあえずrefを使っておけばいい」 くらいの認識でした。
一方で、Vueにはreactiveもあります。
どちらもリアクティブな状態を扱うためのAPIだということは知っていましたが、下記のようなことはあまりよくわかっていませんでした。
-
refとreactiveは何が違うのか - なぜ
refはプリミティブにもオブジェクトにも使えるのか -
refの.valueとは何なのか
結論、基本的には「とりあえずrefを使う」で問題ありません。
今回は、「とりあえずrefを使っておけばいい」と言われるのがなぜなのかを理解します。
refとreactiveの基本的な使い方
最初に、それぞれの基本的な使い方を確認します。
ref
refは次のように使います。
import { ref } from 'vue'
const count = ref(0)
<script setup> などのJavaScriptのコード内でrefが保持している値を取得・変更するときは.valueを使います。
console.log(count.value)
count.value++
reactive
一方、reactiveは次のように使います。
import { reactive } from 'vue'
const state = reactive({
count: 0
})
こちらは.valueを使わず、通常のオブジェクトと同じようにアクセスできます。
console.log(state.count)
state.count++
アクセスの仕方について、.valueが必要か否かという違いがあります。
reactiveではプリミティブを扱えない
もう一つ大きな違いがあります。
refには、
ref(0)
ref('hello')
ref({ count: 0 })
のようにさまざまな値を渡せます。
一方、reactiveでは、公式でもオブジェクト型に対してのみ機能すると記載されていて、文字列、数値、真偽値などのプリミティブ型には使えません。
reactive({ count: 0 }) // OK
reactive(0) // NG
JavaScriptでは、例えば次のような値がプリミティブにあたります。
const count = 0 // number
const name = 'Taro' // string
const enabled = true // boolean
ほかにもundefined、null、bigint、symbolがあります。
https://developer.mozilla.org/ja/docs/Glossary/Primitive
そのため、
reactive({ count: 0 }) // OK
reactive([1, 2, 3]) // OK
reactive(0) // NG
reactive('hello') // NG
となります。
Vue 3のreactive()では、内部でJavaScriptのProxyという仕組みを使ってオブジェクトをリアクティブにしています。
Proxyについての詳細は割愛しますが、Proxyはオブジェクトを対象とする仕組みのため、numberやstringなどのプリミティブはreactive()では扱えません。
では、なぜrefなら0を扱えるのでしょうか。
refはなぜプリミティブも扱えるのか
ポイントは、ref(0)を実行したときに、0そのものが返ってくるわけではないことです。
const count = ref(0)
このcountに入っているのは、refオブジェクトです。
refオブジェクトが保持している値には、.valueからアクセスします。
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
refオブジェクトの中に0を保持して、それを.valueから扱っています。
reactiveは渡されたオブジェクトを対象としたProxyを返します。
先述の通り、Proxyはオブジェクトを対象とする仕組みのためプリミティブを扱えません。
refは渡された値そのものをProxyにするのではなく、値を保持するrefオブジェクトを返します。
そのためオブジェクト以外も扱えるのです。
実はrefにオブジェクトを渡した場合、そのオブジェクトは内部でreactive()によってリアクティブになります。
ref の値としてオブジェクトが代入された場合、reactive() でそのオブジェクトは深いリアクティブになります。これはオブジェクトがネストした ref を含む場合、それが深くアンラップされることも意味します。
https://ja.vuejs.org/api/reactivity-core.html#ref
refにオブジェクトを渡した場合、下記のように扱えます。
const user = ref({
name: 'Taro'
})
console.log(user.value.name)
user.value.name = 'Jiro'
そもそもrefオブジェクトって何?
ref()が返すrefオブジェクトとは、Vueが用意しているリアクティブな値を保持するためのオブジェクトです。
見た目としては次のようなイメージに近いです。
あくまでもイメージで、もちろん実際のrefオブジェクトは単にvalueというプロパティを持ったオブジェクトではありません。
const count = {
value: 0
}
Vueは.valueへのアクセスや変更を検知できるようにしています。
そのため、
count.value
と値を読み取ったり、
count.value = 1
と値を変更したりしたことを、Vue側で把握できます。
つまり、
const count = ref(0)
では、0をrefオブジェクトに保持し、.valueを通して扱うことでリアクティブな状態として利用できるようにしている
と考えると分かりやすそうです。
refのtemplateでは.valueを書かなくていい
JavaScriptのコード内でrefが保持している値を取得・変更するときは.valueを使うとはじめに書きましたが、templateでは、基本的に.valueが不要です。
JavaScript上では、
const count = ref(0)
console.log(count.value)
と.valueが必要ですが、templateでは次のように書けます。
<template>
<p>{{ count }}</p>
</template>
{{ count.value }}とする必要はありません。
これは、テンプレートの中ではVueがrefを自動的にunwrapしてくれるためです。
テンプレートでのアンラップは、ref がレンダリングコンテキストのトップレベルのプロパティである場合にのみ適用されます。
https://ja.vuejs.org/guide/essentials/reactivity-fundamentals#caveat-when-unwrapping-in-templates
テンプレートではVueがrefの中身を扱いやすくしてくれているということです。
ここまでの違いを整理すると、次のようになります。
ref |
reactive |
|
|---|---|---|
| プリミティブ | ○ | × |
| オブジェクト | ○ | ○ |
| JavaScriptで値にアクセス | .value |
そのまま |
reactiveはいつ使う?
改めて整理してみて、
「refがどちらにも使えるなら、reactiveはいつ使うんだろう?」
と気になります。
結論、Vue公式ドキュメントでも、reactive()にはいくつか制限があることから、リアクティブな状態を宣言する主要なAPIとしてref()を使うことが推奨されており、基本的にはrefを使う方針で問題なさそうです。
制限
- オブジェクト型にしか使えない
- オブジェクト全体を置き換えると、古い参照へのリアクティブな接続が失われる
- プリミティブなプロパティを分割代入すると、リアクティブな接続が失われる
https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive
例えば下記のように、一つのオブジェクトの各プロパティを頻繁に操作する場合には、reactiveを使うと.valueを毎回書かずにすむので、シンプルにできる場合があります。
// reactive: .value不要でスッキリ
const form = reactive({ name: '', email: '', age: 0 })
form.name = '太郎'
form.email = 'test@test.com'
form.age = 10
// ref: .valueを毎回書く必要がある
const form = ref({ name: '', email: '', age: 0 })
form.value.name = '太郎'
form.value.email = 'test@test.com'
form.value.age = 10
※refではオブジェクト全体を.valueに再代入できます
form.value = {
name: '太郎',
email: 'test@test.com',
age: 10
}
まとめ
今回は、普段なんとなく使っていたrefとreactiveの違いを整理しました。
refは値をrefオブジェクトに保持することで、プリミティブ・オブジェクトのどちらも扱えます。
これまで「とりあえずrefを使っておけばいい」と考えていましたが、refとreactiveがそれぞれどのように値を扱っているのか、その上でなぜrefが推奨されているのかを理解できました!