



























If you landed on this post, you probably have a basic understanding of Vue reactivity.
However, like me, you might be asking yourself the eternal question: what are the main differences between ref() and reactive()? And when to use one or another?
Let's find the answer together.
ref() and reactive() handle differently primitive values: strings, numbers, booleans, null, and undefined.
refs() stores both primitive values and objects:
import { ref } from 'vue'
const numberRef = ref(0); // OK
const objectRef = ref({ count: 0 }) // OK
In the example above ref(0) creates a ref storing a primitive value.
Same way, ref({ count: 0 }) creates a ref storing a plain JavaScript object.
On the other side, reactive() doesn't store primitives, but stores only objects:
import { reactive } from 'vue'
const numberReactive = reactive(0); // NOT OK!
const objectReactive = reactive({ count: 0}); // OK
Calling reactive(0) with a primitive value is incorrect. Don't do this. If you need to make reactive primitive values, ref(0) is the way to go.
The reason why reactive() works only with objects is in Vue's reactivity implementation. Vue uses Proxies to intercept property changes on objects. And proxies do not work with primitives.
Nevertheless, reactive({ count: 0}) initialized with an object is perfectly valid and creates a reactive object.
In conclusion:
ref()can store primitive values, whilereactive()cannot.
The second difference is how you'd access the data stored inside ref() and reactive().
ref() data, either a primitive value or an object, is accessed through a special property .value:
import { ref } from 'vue'
const numberRef = ref(0);
console.log(numberRef.value); // logs 0
const objectRef = ref({ count: 0 })
console.log(objectRef.value.count); // logs 0
numberRef.value is how you access the primitive value from the ref numberRef.
<ref>.value is a special property available on all the refs to read or update the ref value.
Also, objectRef.value.count is how you can access a property of an object in ref.
Note that inside templates you don't have to use .value to access a ref value: they're auto-unwrapped.
<script setup>
import { ref } from 'vue'
const numberRef = ref(0)
</script>
<template>
<div>{{ numberRef }}</div> <!-- <div>0</div> -->
</template>
In the interpolation {{ numberRef }} the ref is auto-unwrapped.
reactive() data, on the other hand, is accessed directly:
import { reactive } from 'vue'
const objectReactive = reactive({ count: 0})
console.log(objectReactive.count) // logs 0
Accessing reactive data created using reactive({ count: 0} ) doesn't need additional syntax and is done directly: objectReactive.count.
The reactive object returned by reactive(originalObject) is a proxy object of originalObject. Meaning that the reactive object has the same properties (aka has the same interface) as the originalObject.
In conclusion:
ref()data is accessed usingvalueproperty (exception: in templates the ref is auto-unwrapped), whilereactive()data is accessed directly.
ref() is accessed and updated using .value property, while reactive() is a proxy of the original object. As result ref() can be reassigned to an new object, while reactive() cannot.
Reassigning the value of a ref() entirely to a new value is perfectly valid:
<script setup>
import { ref, onMounted } from "vue"
const objectRef = ref({ count: 0 })
onMounted(() => objectRef.value = { count: 1 })
</script>
<template>{{ objectRef.count }}</template>
Open the demo, and see that replacing entirely the ref value objectRef.value = { count: 1 } after mounting is reflected in the output. The reactivity is kept.
Reassigning entirely a reactive() object, however, is not possible:
<script setup>
import { reactive, onMounted } from 'vue'
let objectReactive = reactive({ count: 0})
onMounted(() => objectReactive = { count: 1 })
</script>
<template>{{ objectReactive.count }}</template>
Open the demo, and see that replacing entirely the reactive object value objectReactive = { count: 1 } after mounting is not reflected in the output. Doing so breaks the reactivity of objectReactive.
In conclusion:
ref()value can be reassigned entirely to a new object, while areactive()cannot.
A direct consequence of ref data being accessed through .value property is how refs are typed.
To annotate a ref you need to use a special type Ref, available for importing from vue library:
import { ref, Ref } from 'vue'
const numberRef: Ref<number> = ref(0);
Ref<number> is the type meaning a ref holding a number.
If you want to assign a ref as an argument to a composable, for example, then make sure to use the Ref<V> type (where V is the value's type) to annotate a ref parameter:
import { ref, Ref } from 'vue'
const numberRef: Ref<number> = ref(0)
export const useIsEven = (numberRef: Ref<number>) => {
return computed(() => numberRef.value % 2 === 0)
}
const isEven = useIsEven(numberRef) // type check passed
On the other hand, reactive data returned by reactive() is typed as the initial object:
import { reactive } from 'vue'
const objectReactive: { count: number } = reactive({ count: 0})
reactive({ count: 0}) returns an object of type { count: number }. The reactive object normally keeps the type of the original object.
But there's one exception — if the reactive object contains refs, then these refs are unwrapped.
import { reactive, ref } from 'vue'
const objectReactive: { count: number } = reactive({ count: ref(0)})
Even though the reactive object is { count: ref(0) }, the returned type is still { count: number }. All because reactive() automatically unwraps the refs found in the reactive object.
In conclusion:
refs returned by
ref(value: T)are of typeRef<T>, while reactive objects returned byreactive(object: T)are of typeT(exception: refs in reactive are unwrapped).
watch() watches reactive data change. The default behavior of watch() differs for ref() and reactive().
watch() determines if .value property of the ref is changed:
<script setup>
import { ref, watch } from 'vue'
const countNumberRef = ref(0)
watch(countNumberRef, () => {
console.log('changed!')
})
const increase = () => countNumberRef.value++
</script>
<template>
{{ countNumberRef }}
<button @click="increase">Increase</button>
</template>
Every time you click the "Increase" button, you'll see in the console the message "changed!". watch(count, callback) calls callback every time countNumberRef.value changes.
But does watch() watch deep changes of an object stored in ref()? Let's try!
<script setup>
import { ref, watch } from 'vue'
const countObjectRef = ref({ count: 0 })
watch(countObjectRef, () => {
console.log('changed!')
})
const increase = () => countObjectRef.value.count++
</script>
<template>
{{ countObjectRef.count }}
<button @click="increase">Increase</button>
</template>
This time, however, if you click the "Increase" button there will be no message in the console! The conclusion is that watch() doesn't deeply watch refs by default.
However, DOM still updates when countObjectRef.value.count changes: which means the object in the ref is still reactive in regards to the rendered output.
Of course, if you ask watch() to watch the ref deeply, it's working as full deep watch:
// ...
watch(count, () => {
console.log('changed!')
}, { deep: true })
// ...
In the case of watching a reactive object, watch() always performs a deep watch (even if you don't indicate { deep: true }) option.
<script setup>
import { reactive, watch } from 'vue'
const countObjectReactive = reactive({ counter: { val: 0 } })
watch(countObjectReactive, () => {
console.log('changed!')
})
const increase = () => countObjectReactive.counter.val++
</script>
<template>
{{ countObjectReactive.counter.val }}
<button @click="increase">Increase</button>
</template>
Every time you click the "Increase" button, you'll see in the console the message "changed!". watch(countObjectReactive, callback) calls callback every time any property (even a deep one) of countObjectReactive changes.
In conclusion:
watch()by default watches only direct.valuechange ofref(), while doing a deep watch of areactive()object.
While there isn't a strict rule, still, there are situations when using a specific reactivity function is preferable:
ref() is the right choice.ref() is a good option.reactive() is a good option.This post presented the differences between ref() and reactive() in composition API:
ref() can store a primitive value, while reactive() cannot.ref() using <ref>.value, while reactive() object can be used directly as a regular object.ref() value can be reassigned to an entirely new object, while reactive() cannot.ref() is typed as Ref<V>, while the reactive object returned by reactive(originalObject) usually maintains the type of the originalObject.watch() (when used without options) normally watches only direct changes of <ref>.value, while watching deeply reactive() objects.You'd use ref() to store primitives or value objects, but reactive() if you're interested to watch deep changes of a mutable object.
What other differences between ref() and reactive() do you know?
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。