おはようございます!そうすけです!
運営者:そうすけ
30代のブロガーとエンジニア。
工場勤務→情シス→ソフトウェアエンジニア
主にフロントエンド開発を行っています。
趣味はガジェットと植物。
Vue使っている皆さんが当たるのが、RefとReactiveの使用の壁です。
本記事はReactiveとRefの違いを改めて考えてみます。
Vue公式ドキュメントの【リアクティビティーの基礎】や他の記事を参考にしています。
RefとReactiveの基礎
Ref関数:プリミティブ型でも、オブジェクト型を監視可能する
プリミティブ型(String、Number、Booleanなど)でもオブジェクト(配列や連想配列)でも対応可能です。
<template>
//refを代入した変数をそのまま表示
<div>{{name}}</div>
//オブジェクトの場合は.valueでアクセスが必要
<div>{{family.value.mother.age}}</div>
</template>
<script>
const name = ref("sousuke")
const family = ref({
mother:{age:35},
father:{age:36}
})
</script>
テンプレート内で ref を使用し、後から ref の値を変更した場合、Vue は自動的にその変更を検出し、それに応じて DOM を更新します。
これは、依存関係追跡ベースのリアクティビティーシステムによって実現されています。
コンポーネントが初めてレンダリングされるとき、Vue はレンダリング中に使用されたすべての ref を追跡します。その後 ref が変更されると、それを追跡しているコンポーネントの再レンダリングがトリガーされます。
リアクティブになっている所以は、getter、setterが内包されたオブジェクトだから再レンダリングされるようです。
.value
プロパティは、Vue に、ref がアクセスされたり変更されたタイミングを検出する機会を提供します。Vue は、getter でトラッキングを行い、setter でトリガーを実行する仕組みになっています。概念的には、ref は次のようなオブジェクトと考えることができます:
// 実際の実装ではなく、疑似コード
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Reactive関数
オブジェクト型のみ監視可能です。
プリミティブ型は使用できず、必ず{キー:値}の形式であるオブジェクトの必要があります。
import { reactive } from 'vue'
const state = reactive({ count: 0 })
template
<button @click="state.count++">
{{ state.count }}
</button>
気を付けるべきところ
Ref注意点
- 配列の場合valueがないとアンラップされない
<templete>
//オブジェクトの場合は.valueでアクセスが必要
<div>{{family.value.mother.age}}</div>
</templete>
<script>
const count = ref(0)
console.log(state.count) // 0
const books = reactive([ref('Vue 3 Guide')])
// ここでは .value が必要
console.log(books[0].value)
const family = ref({
mother:{age:35},
father:{age:36}
})
</script>
refの場合、オブジェクト自体がラップされるので、内部のプロパティにアクセスするには一度valueを付けないとプロパティにアクセスできません。
- テンプレートのアンラップはトップレベルのプロパティでないとリアクティブに監視されない
const count = ref(0)
const object = { id: ref(1) }
この表現は期待通りに動作します:
template
{{ count + 1 }}
……一方、こちらは動作しません:
template
{{ object.id + 1 }}
レンダリング結果は [object Object]1 となります。これは、式の評価時に object.id がアンラップされず、ref オブジェクトのままだからです。これを修正するには、id をトップレベルのプロパティに分割代入すればいいのです:
js
const { id } = object
template
{{ id + 1 }}
template
内で表示する場合の書き方
Vueのテンプレート内で直接user.profile.name
のようにアクセスすることはできません。この場合は、.value
を明示する必要があります:
const user = ref({
profile: {
name: 'Alice',
details: { age: 25 }
}
});
<p>{{ user.value.profile.name }}</p>
<p>{{ user.value.profile.details.age }}</p>
Reactiveの気を付けるべき制限
オブジェクト全体を置換できない
Reactiveはあくまでオブジェクトではなくプロパティを追跡をするような設計思想ので、オブジェクト全体の置換は検知できません。
プロパティにrefを使われているようなイメージです。
let state = reactive({ count: 0 })
// 上記の参照({ count: 0 })は、もはや追跡されていません
// (リアクティブな接続は失われました!)
state = reactive({ count: 1 })
分割代入できない
分割代入して扱うとプロパティとしてリアクティブが失われます。
const state = reactive({ count: 0 })
// count は分割代入すると state.count と切り離されます。
let { count } = state
// 元の状態に戻りません。
count++
// この関数は数値を受け取りますが、これだと
// state.count の変更を追跡することができません。
// リアクティビティーを維持するためには、オブジェクト全体を渡す必要があります
callSomeFunction(state.count)
型が自動補完でわからない
reactive
メソッドの返り値の型は元のオブジェクトのままです。
なので、変数の型情報を見ただけではそれが通常のオブジェクトなのか、リアクティブな値なのか判別できません。
リアクティブなデータを ref
で定義した場合になら Ref<number>
のように推論されるので、型情報を見るだけでリアクティブなデータだと認識できます
結論
- 基本方針としては
ref
に統一するのが理にかなっており、特に型安全性やコードの一貫性を保てる。 - 深いネストや複雑なオブジェクトの管理が必要な場合には、
reactive
を補助的に検討することも選択肢として考慮する。
refでもreactiveでも同等のことができ、refのほうが型安全であるのでrefを推奨
オブジェクトならtemplate内に書くときに冗長になるものの、オブジェクトであることを判断しやすいです。
Vue 3ではref
もreactive
もリアクティブなデータ管理ができ、型安全性の点からもref
を使うメリットが増えています。
Vueの公式でも、プリミティブな値や単一オブジェクトのデータの監視にはref
が推奨されており、ref
はそのままの型情報を保持するため型安全です。
また、Vue 3では ref
のオブジェクトでも .value
経由でアクセスすれば、プロパティ変更も基本的にリアクティブとして扱われるため、全体の型安全性やコードの一貫性を保つためにref
に統一するのがいいアプローチかと。
ただし、オブジェクトの深いネストを伴うケースやプロパティごとの追跡が必要な場合には、必要に応じてreactive
も検討することも視野に入れて、適材適所で使い分けるのが理想的です。
コメント