2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なぜ「とりあえずref」でいいのか?reactiveとの違いを改めて整理する

2
Posted at

はじめに

VueのComposition APIで状態を管理するとき、refreactiveを使います。
例えば、次のようなコードです。

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だということは知っていましたが、下記のようなことはあまりよくわかっていませんでした。

  • refreactiveは何が違うのか
  • なぜrefはプリミティブにもオブジェクトにも使えるのか
  • ref.valueとは何なのか

結論、基本的には「とりあえずrefを使う」で問題ありません。
今回は、「とりあえずrefを使っておけばいい」と言われるのがなぜなのかを理解します。

refreactiveの基本的な使い方

最初に、それぞれの基本的な使い方を確認します。

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

ほかにもundefinednullbigintsymbolがあります。
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はオブジェクトを対象とする仕組みのため、numberstringなどのプリミティブは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を使う方針で問題なさそうです。

制限

  1. オブジェクト型にしか使えない
  2. オブジェクト全体を置き換えると、古い参照へのリアクティブな接続が失われる
  3. プリミティブなプロパティを分割代入すると、リアクティブな接続が失われる
    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
}

まとめ

今回は、普段なんとなく使っていたrefreactiveの違いを整理しました。
refは値をrefオブジェクトに保持することで、プリミティブ・オブジェクトのどちらも扱えます。

これまで「とりあえずrefを使っておけばいい」と考えていましたが、refとreactiveがそれぞれどのように値を扱っているのか、その上でなぜrefが推奨されているのかを理解できました!

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?