5 Must-Know Differences Between ref() and reactive() in Vue
source link: https://dmitripavlutin.com/ref-reactive-differences-vue/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
If you landed on this post most likely you 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 would you use one or another?
Let's find the answer together.
1. Primitive values
ref()
and reactive()
handle differently primitive values: strings, numbers, booleans, null
, and undefined
.
1.1 ref()
refs()
stores both primitive values, as well as 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.
1.2 reactive()
On the other side, reactive()
doesn't store primitives, but stores only objects:
import { reactive } from 'vue'
const objectReactive = reactive({ count: 0}); // OK
Calling reactive(0)
with a primitive value is invalid. 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. However, 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.
2. Accessing reactive data
The second difference is how you'd access the data stored inside ref()
and reactive()
.
2.1 ref()
ref()
data, either a primitive value or an object, is accessed through a special property .value
:
import { ref } from 'vue'
const numberRef = ref(0);
const objectRef = ref({ count: 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.
In the template, you don't have to use .value
to access a ref value. This is also called ref auto-unwrapping in templates:
<script setup>
import { ref } from 'vue'
const numberRef = ref(0);
</script>
<template>
</template>
{{ numberRef }}
reads the ref value directly.
2.2 reactive()
reactive()
data, on the other hand, is accessed directly:
import { reactive } from 'vue'
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
. This means that the reactive object has the same properties (aka has the same interface) as the originalObject
.
In conclusion:
ref()
data is accessed usingvalue
property (exception: in templates the ref is auto-unwrapped), whilereactive()
data is accessed directly.
3. Typing
3.1 ref()
A direct consequence of ref data being accessed through .value
property is refs typing.
To annotate a ref you need to use a special type Ref
, which is 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)
return computed(() => numberRef.value % 2 === 0)
const isEven = useIsEven(numberRef) // type check passed
3.2 reactive()
On the other hand, reactive data returned by reactive()
is typed like the initial object:
import { reactive } from 'vue'
const objectReactive: { count: number } = reactive({ count: 0});
reactive({ count: 0})
returns an object of type { count: number }
, which exactly represents the reactive object.
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).
4. Watching
watch()
watches reactive data change. The default behavior of watch()
differs for ref()
and reactive()
.
ref()
watch()
without problems determines if .value
property of the ref was changed:
<script setup>
import { ref, watch } from 'vue'
const countNumberRef = ref(0)
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 })
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 perform a deep watch by default on refs.
While do note that DOM still updates while countObjectRef.count
: meaning that the object in ref is still reactive in regards of the rendered output.
Of course, if you ask watch()
to watch the ref deeply, it's going to work as expected:
// ...
watch(count, () => {
console.log('changed!')
// ...
reactive()
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'
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.value
change ofref()
, while doing a deep watch of areactive()
object.
5. Usage
Finally, and probably one of the most important difference to know, is when you'd use ref()
and when reactive()
?
While there isn't a strict rule, still, there are situations when using a specific reactivity function is preferable:
- If you need a reactive primitive value, then using
ref()
is the right choice. - If you need a reactive value object (an object whose properties usually don't change), then using
ref()
is a good option. - If you need a reactive mutable object, and you want to track even the deeply mutated properties of that object, then using
reactive()
is a good option.
6. Conclusion
This post presented the differences between ref()
and reactive()
in composition API.
Let's review the conclusion:
ref()
can store a primitive value, whilereactive()
cannot.- You access the value stored in a
ref()
using<ref>.value
, whilereactive()
object can be used directly as a regular object. ref()
is typed asRef<V>
, while the reactive object returned byreactive(originalObject)
usually maintains the type of theoriginalObject
.watch()
(when used without options) normally watches only direct changes of<ref>.value
, while watching deeplyreactive()
objects.- You'd use
ref()
to store primitives or value objects, whilereactive()
if you're interested to watch deep changes of a mutable object.
What other differences between ref()
and reactive()
do you know?
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK