Vue3 の reactive vs ref
概要
最近 this vs that を読んで、フロントエンドの理解を深めています. TypeScript の「interface vs type」や CSS の「display: none vs [hidden]」は、どちらが良いのか初学者には難しいですよね💦.
ただとても勉強になる一方、React の項目はあるのに Vue.js の項目がないです.
そこで、この記事では Vue.js の this vs that として reactive と ref について違いをまとめていこうと思います.
Vue3 のリアクティブについて
Vue3 のリアクティブは、Proxy
を使って実現しています.Proxy
を使うことで、ある変数が変更されるとそれに依存する変数も再計算されます.
// 使い方のイメージ
const data = new Proxy({
value: 1
}, {
set: function(obj, prop, newValue) {
obj[prop] = newValue;
func();
}
});
let value = 2 * data.value
const func = () => {
value = 2 * data.value
}
console.log(value); // 2
data.value = 4;
console.log(value); // 8
ここで大事なのは、 Proxy
に渡すリアクティブにしたい変数を オブジェクト にしている点です.
FYI: Vue2 では、Proxy
ではなく Object.defineProperty
を使っていました. Object.defineProperty
と Proxy
は違う記事で書こうと思います.
reactive vs ref
結論、 オブジェクトをリアクティブにする場合は reactive
、プリミティブ型をリアクティブにする場合は、ref
を使えば良いです.
以下、コードベースで実装を追いながら説明します. まず、最初に reacrive
のコードを見ていきましょう.
export function reactive(target: object) {
// 省略
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 省略
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
分かりやすくするために、コードを省略していますが大雑把に以下の流れで処理します.
-
createReactiveObject
が呼ばれる - 引数の
target
がオブジェクトかどうか判定する - オブジェクトの場合は
Proxy
の引数に渡される -
Proxy
オブジェクトが返される
次に ref
のコードを見てみましょう.
export function ref(value?: unknown) {
return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val
-
createRef
を呼ぶ - 渡ってきた値を元に
RefImpl
オブジェクトを作る -
RefImpl
では_value
からvalue
のget
,set
を定義している -
RefImpl
オブジェクトが返される
本当は track
や trigger
のコードを追って、どのようにリアクティブ処理を最適化しているか見るべきですが記事が長くなるので省略します.
整理すると、次のようになります.
reactive | ref |
---|---|
値がオブジェクトなら Proxy 型にして返す |
値を元に RefImpl 型にして返す. RefImpl では value プロパティがあり、リアクティブになる |
このような特徴のため、最初に書いたオブジェクトなら reactive
、プリミティブ型なら ref
が良いです.
ref
には一つ注意点があり <script>
タグ内で使う場合は、value
プロパティを忘れないようにしてください.
const data = ref(0);
console.log(data.value); // 0
Discussion