Composableを作るとき、
- リアクティブな値とリアクティブじゃない値の両方を公開したい
- 使う側で分割代入せず使いたい
-
.valueを書きたくない
のような場合にproxyRefsが便利です。
proxyRefsは、プロパティにRefがあるとアンラップして扱ってくれます。
export default () => {
const count1 = ref(0)
const data = reactive({
count2: 0,
count3: 0,
})
const incrementAll = () => {
count1.value++
data.count2++
data.count3++
}
return proxyRefs({
count1,
...toRefs(data),
incrementAll,
})
}
すると使う側は .value 不要でアクセスできます。
const counter = useCounter()
counter.count1++
counter.count2++
counter.count3++
counter.incrementAll()
reactive() だけではダメ?
例えば以下のように全体を reactive() にすることもできます。
return reactive({
count1: 0,
count2: 0,
count3: 0,
incrementAll
})
ただ、この場合は関数までリアクティブオブジェクトに含まれる形になります。
実害はあまりないと思いますがちょっと気持ち悪いです。
getter / setter ではダメ?
以下の書き方でもvalueを省略できますしreactive性も保てます。
export default () => {
// 中略
return {
get count1() {
return count1.value
},
set count1(v) {
count1.value = v
},
get count2() {
return count2.value
},
set count2(v) {
count2.value = v
},
incrementAll
}
}
しかしstateが増えると冗長です。
注意点1: 分割代入するとリアクティブ性が失われる
proxyRefs は「オブジェクト経由アクセス」で .value を省略しているだけです。
そのため、以下のように分割代入するとリアクティブ性が失われます。
const { count1 } = useCounter()
console.log(count1) // number
分割代入して使いたい場合は、toRef / toRefs を使って取り出す必要があります。
注意点2: TypeScript上でreadonly判定が失われる
readonlyやcomputedを使って読み取り専用プロパティを返しても、proxyRefsを通すとreadonly判定が無くなってしまいます。
export default () => {
// 中略
return proxyRefs({
...toRefs(readonly(data)),
computedVar,
increment
})
}
実際には書き込めませんが、型エラーにはなってくれません。
const counter = useCounter()
counter.count
counter.count = 123 // 実行時にwarning
そのため、必要に応じて返り値型を明示するなどする必要があります。