Vue2しかやってこなくてたまたまVue3のソースに触れた時、
たった一つのバージョンが変わるだけでこんなに変わるの・・・ってなりました。
その一つがこのrefというやつです。
Vue2の頃はただ変数を書くだけでしたが、
Vue3だと定義された変数にrefというやつがついていたり、ついていなかったりします。
このrefってのが何をしているのかを記事にしたいと思います。
refってなに
refとは一言で表すと、「Vue3でリアクティブな変数を定義する時に使うやつ」です。
厳密には、Vue3のComposition APIという記法の中で使われている一要素でリアクティブな値の実現させている機能です。
一応リアクティブとは何かというのも説明しておくと、
「値が監視されていて、その値が更新された時に変更が検知される状態のこと」です。
値の変更を監視することで、値が変更した瞬間リアルタイムで処理を走らせ画面に反映させたりします。
ちなみにVue3だとreactiveっていうのでもリアクティブな変数は定義可能ですが、それについても解説します。
refの使い方
では、実際に値をリアクティブにしている例を出します。
基本的には、値をref(値)のように囲んであげる感じです。
<script setup>
import { ref } from 'vue'
// これでnameはリアクティブな値として扱われる
const name = ref('Tanaka')
</script>
定義方法はこれだけです。
リアクティブなデータとして定義できたら実際に使っていきましょう。
コンソール出力してみます。
<script setup>
import { ref } from 'vue'
const name = ref('Tanaka')
// refで定義した値はvalueを使わないと中身を取り出せない。
console.log(name.value)
</script>
// Tanakaと出力される。
// valueを使わないと、実際はRefにラッピングされているので、Tanakaまで辿り着かない。
ポイントとしては、基本的にrefでラッピングされた値はそのままじゃ定義したデータは使えません。
valueというプロパティを使ってrefの中身を取り出して使います。
Vue3はこのようにrefで値をラッピングすることで正確なリアクティブの機能を実現させています。
refを使ってリアクティブを実現する理由は公式だと以下のようなことが書いてあります。(かるくまとめてます)
JavaScriptにおけるnumberやstringの値はプリミティブ型と呼び、参照ではなく実際の値をやり取りするため、オブジェクト型と比べると若干動作が異なっています。
そこでリアクティブにしたい値を全てrefでラッピングすることで、どんなデータの形であっても同じ動作をさせてリアクティブを機能させることができます。
なのでどんな値でもラッパーオブジェクトとなるので、途中でリアクティブでなくなることがなく、アプリ全体に安全に渡すことができる。
ということみたいです。
ちょっとだけ大事な例外
つい先ほどrefで定義した値は、valueのプロパティを使って初めて定義した実際のデータにアクセスできると書きましたが、valueなんか使わなくても値が取り出せるパターンがあります。
templateの中です。
templateの中だとrefでラッピングしていても自動でアンラップしてくれて、実際のデータを引っ張り出してくれます。
<template>
<div>
{{ name }}
<div>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('Tanaka')
</script>
// valueを使わずとも、画面にはTanakaと表示される。
<script>
ブロックの中でリアクティブに定義したnameが、先ほどまでの処理で使うときはname.valueと書く必要がありましたが、<template>
ブロックだとnameだけでTanakaのデータを使えています。
これがとても大事な例外です。
これのせいでrefの使い方に混乱を招きがちな気はするんですが、
基本的にはvalueでデータにアクセス。しかし、templateで用いるときはそのまま利用可能という風に覚えておきましょう。
reactiveという選択肢
実はref以外にもデータをリアクティブにする方法があります。
それがreactiveというやり方です。
(Vue2でのVue.observable()
というAPIの名称を変えたものみたいです。)
使い方はこんな感じです。
import { reactive, computed } from 'vue'
const dinner = reactive({
name: "tamagoyaki"
price: 300
})
// refと違ってそのまま使えて、かつ値はリアクティブである。
const addTax = computed(() => {
return dinner.price * 2;
});
こんな感じでreactiveで囲むことで、定義する値をまとめてリアクティブなものとして定義できます。
上の例ではプリミティブな値しかリアクティブにしていませんが、reactiveはディープに変換してくれるので、オブジェクトを定義してもリアクティブに変えてくれます。
ディープに変換っていうのは、どんなにネストしてても全部対応してくれるよみたいな感じです。
そしてreactiveとrefとの大きな違いは、
定義の方法と、valueを使わなくても値が取り出せることです。
valueいらずとなるのは便利だと思います。
しかし、valueがいらないということは直感的に使える反面、
refでの定義であればリアクティブな値とそうでない値の見分けが一目でついていましたが、
reactivedの定義だとソースを追わないとリアクティブかどうかがわからない可能性もあったりで一長一短かと思います。
reactiveのデメリット
あとreactiveのデメリットを一応ご紹介しておくと、reactiveで定義された値は分割代入ができません。
以下の例のようなコードだと、値がリアクティブじゃなくなってしまいます。
// リアクティブな値を定義
const obj = reactive({
count: 1,
});
// 分割代入によってリアクティブでなくなる
const { count } = obj;
// obj.countが変更されても反応しません
const doubleCount = computed(() => {
return count * 2;
});
もし分割代入がしたい場合は、後で紹介するtoRefsを使う必要があります。
refかreactiveか
Vue3においてリアクティブな値を定義するときは、基本的にはこの二択だと思います。
そしてどちらを使うべきなのかという議論はいろんな記事で見かけました。
というのも公式ではまだこの二つの使い分けについては模索中のようなので、
各々で結論や方針を決める必要があり、色んな意見が出てるみたいです。
一応公式の中で、プリミティブな値はrefでオブジェクトに対してはreactiveというように使い分けた方がいい。みたいな記述もあるんですが・・・
ベストプラクティスはもう少し広まってから確立されそうです。
なのでとりあえずはこんな機能あるんだなあという理解をしておき、
それぞれの良さを理解した上で使いたい方を使うといいと思います。
個人的には、可読性を取って全てrefで定義しておく方が安全だと考えてはいますが、
refかreactiveかについてはプロジェクト内でルールはきちんと決めておくべきではあるでしょう。
(他にもreactive定義だと初期値設定がすごい複雑になってTypeScriptとの相性よくない気が最近してます・・・)
refに関連する他の機能
refには関連したメソッドや機能がちょこちょこあります。それを紹介していきます。
公式もリンク置いておきます。細かい点はこっちを見てください。
toRef
これと後で紹介するtoRefsは主にreactiveで定義したリアクティブな値向けの機能です。
toRefはreactiveで定義した値を一つrefがついた状態で取り出すことができます。
公式の例を見てもらうと早いと思います。
// リアクティブな値を定義
const state = reactive({
foo: 1,
bar: 2
})
// state.fooをrefをつけたデータとして取り出し、代入する
const fooRef = toRef(state, 'foo')
fooRef.value++
*console*.log(state.foo) // 2
// 元のデータとのリアクティブな接続は保たれている
state.foo++
console.log(fooRef.value) // 3
リアクティブ性を維持したまま値を取り出したい時に使ってください。
toRefs
toRefは値を一つだけrefにしてくれましたが、toRefsはreactiveで定義されたプロパティ全てにrefをつけて返してくれます。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs の型:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref と元のプロパティは「リンク」している
state.foo++
*console*.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
*console*.log(state.foo) // 3
ちなみにtoRefsは値を全てrefでラッピングしてくれるので、上手く使えばreactiveで定義した値を分割代入で使えます。
const state = reactive({
foo: 1,
bar: 2
})
// toRefsで全てrefで包まれた値に変わっているので、分割代入してもリアクティブ性が保たれている。
const {foo} = toRefs(state)
// refになってるのでvalueは忘れずに
console.log(foo.value) // 1
unref
これは今使おうとしている値がrefで定義されているデータかそうでないデータかわからない時に使います。
refで定義されたデータであれば自動的にvalueをつけて中身を取り出してくれますし、
refでなければデータをそのまま使ってくれます。
// refで包まれたデータ
const foo = ref(1)
// 普通のデータ
const bar = 2
// 自動でvalueを補って出力
console.log(unref(foo)) // 1
// そのまま出力
console.log(unref(bar)) // 2
isRef
渡されたものがrefで包まれているか確認します。それだけです。
当たり前ですが、一応reactiveで定義されたデータには反応しないということも補足しておきます。
// refで定義
const foo = ref("foo");
// reactiveで定義
const bar = reactive({
bar: "bar"
});
// refなのでtrue
isRef(foo) // true
// refじゃないのでfalse
isRef(bar) // false
isReactive
reactiveで定義された値か見てくれるやつですね。
(ここは他にもisReadonlyやらisProxyやらあるんですが、この記事で書くとややこしいのであえて書かないでおきます。そんなに難しくはないので気になる方は公式読んでください。)
readonly
これは、先ほどまでと少し勝手が違いますが、もしかしたら使う機会があるかもなので紹介しておきます。
reactiveで定義された値をリアクティブな状態のまま、中身のデータを変更されたくない場合に使います。
読み取り専用のデータにしちゃうという感じですね。
例を見た方が早いと思います。
import { reactive, readonly } from 'vue'
// リアクティブな値を定義
const original = reactive({ count: 0 })
// リアクティブだけど読み取り専用のデータを作成
const copy = readonly(original)
// original を変更すると copy 側の依存ウォッチャが発動します
original.count++
// copy を変更しようとすると失敗し、警告が表示されます
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
他にもshallowRef
とか色々ありますが、少しマニアックなので気になる方は公式を見てください。
まとめ
もっサクっと書けるかと思っていましたが、意外とボリューミーな内容になりました・・・
結局はVue3でリアクティブな値の定義の方法というだけなので、
一旦は基本の使い方だけを押さえておけば最初は何も困らないかなと思います。
しかしrefにせよreactiveにせよ、きちんと使い方のルールを決めておかないと、
今自分が使おうとしてるデータはリアクティブなのか、そもそもrefなのか、分割代入してもいいんだっけ、みたいな小さな悩み事が増えてしまい、ぐっと開発効率が落ちてしまいます。
私自身も使い方をしっかり模索していきたいと思います。
参考
・公式1
・公式2
・公式3
・公式4