Ref vs. Reactive: What to Choose Using Vue 3 Composition API?
I love Vue 3's Composition API, but it provides two approaches to adding a reactive state to Vue components: ref
and reactive
. It can be cumbersome to use .value
everywhere when using refs but you can also easily lose reactivity when destructuring reactive objects created with reactive
.
In this article, I'll explain how you can choose whether to utilize reactive
, ref
, or both.
TL;DR: Use
ref
by default andreactive
when you need to group things.
Reactivity in Vue 3
Before I explain ref
and reactive
, you should understand Vue 3's reactivity system basics.
Info
You can skip this chapter if you already know how Vue 3's reactivity system works.
Unfortunately, JavaScript is not reactive per default. Let's take a look at the following code example:
In a reactivity system, we expect that total
is updated each time price
or quantity
is changed. But JavaScript usually doesn't work like this.
You might ask yourself, why does Vue need a reactivity system? The answer is simple: The state of a Vue component consists of reactive JavaScript objects. When you modify them, the view or dependent reactive objects are updated.
Therefore, the Vue framework had to implement another mechanism to track the reading and writing of local variables, and it's done by intercepting the reading and writing of object properties. This way, Vue can track a reactive object's property access and mutations.
Due to browser limitations, Vue 2 used getters/setters exclusively to intercept properties. Vue 3 uses Proxies for reactive objects and getters/setters for refs. The following pseudo-code shows the basics of property interception; it should explain the core concept and ignores many details and edge cases:
The get
/ set
methods of the proxy are often called proxy traps.
I recommend reading the official documentation for more details about Vue's reactivity system.
reactive()
Let's now analyze how you can use Vue 3's reactive()
function to declare a reactive state:
This state is deeply reactive by default. If you mutate nested arrays or objects, these changes will be detected by Vue:
Limitations of reactive()
The reactive()
API has two limitations:
The first limitation is that it only works on object types like objects, arrays, and collection types such as Map
and Set
. It doesn't work with primitive types such as string
, number
, or boolean
.
The second limitation is that the returned proxy object from reactive()
doesn't have the same identity as the original object. A comparison with the ===
operator returns false
:
You must always keep the same reference to the reactive object otherwise, Vue cannot track the object's properties. You might be confronted with this problem if you try to destructure a reactive object's property into local variables:
Luckily, you can use toRefs
to convert all of the object's properties into refs first, and then you can destructure without losing reactivity:
A similar problem occurs if you try to reassign reactive
value. If you "replace" a reactive object, the new object overwrites the reference to the original object, and the reactive connection is lost:
The reactivity connection is also lost if we pass a property into a function:
ref()
Vue provides a ref()
function that addresses the limitations of reactive()
.
ref()
is not limited to object types but can hold any value type:
To read & write the reactive variable created with ref()
, you need to access it with the .value
property:
You might ask yourself how ref()
can hold primitive types as we just learned that Vue needs an object to be able to trigger the get/set proxy traps. The following pseudo-code shows the simplified logic behind ref()
:
When holding object types, ref
automatically converts its .value
with reactive()
:
Info
If you want to go deeper, take a look at the ref()
implementation in Vue's source code.
Unfortunately, it's also not possible to destructure a reactive object created with ref()
. It leads to loss of reactivity as well:
But reactivity is not lost if refs are grouped in a plain JavaScript object:
Refs can also be passed into functions without losing reactivity.
This capability is quite important as it is frequently used when extracting logic into Composable Functions
A ref
containing an object value can reactively replace the entire object:
Unwrapping refs()
It can be cumbersome to use .value
everywhere when using refs but we can use some helper functionality.
unref Utility function
unref() is a handy utility function that is especially useful if your value could be a ref
. Calling .value
on a non-ref
value would throw a runtime error, unref()
comes in handy in such situations:
unref()
returns the inner value if the argument is a ref
, otherwise, it returns the argument itself. It's a sugar function for val = isRef(val) ? val.value : val
.
Template unwrapping
Vue automatically "unwraps" a ref
by applying unref() when you call it in a template. This way you never need to use access .value
in the template:
Info
This only works if the ref
is a top-level property in the template.
Watcher
We can directly pass a ref
as a watcher dependency:
Volar
If you are using VS Code, you can configure the Volar extension to automatically add .value
to refs. You can enable it in the settings under Volar: Auto Complete Refs
:
The corresponding JSON setting:
Info
To reduce CPU usage, this feature is disabled by default.
Summarizing comparison between reactive() and ref()
Let's take a summarizing look at the differences between reactive
and ref
:
reactive | ref |
---|---|
👎 only works on object types | 👍 works with any value |
👍 no difference in accessing values in <script> and <template> | 👎 accessing values in <script> and <template> behaves differently |
👎 re-assigning a new object "disconnects" reactivity | 👍 object references can be reassigned |
🫱 properties can be accessed without .value | 🫱 need to use .value to access properties |
👍 references can be passed across functions | |
👎 destructured values are not reactive | |
👍 Similar to Vue 2’s data object |
My Opinion
What I like most about ref
is that you know that it's a reactive value if you see that its property is accessed via .value
. It's not that clear if you use an object that is created with reactive
:
This assumption only is valid, if you have a basic understanding of ref
and know that you read the reactive variable with .value
.
If you are using ref
you should try to avoid using non-reactive objects that have a value
property:
If you are new to Composition API, reactive
might be more intuitive and it is quite handy if you try to migrate a component from Options API to Composition API. reactive
works very similarly to reactive properties inside of the data
field:
You can simply copy everything from data
into reactive
to migrate this component to Composition API:
Composing ref and reactive
A recommended pattern is to group refs inside a reactive
object:
If you don't need the reactivity of the state
object itself you could instead group the refs in a plain JavaScript object.
Grouping refs results in a single object that is easier to handle and keeps your code organized. At a glance, you can see that the grouped refs belong together and are related.
Info
This pattern is also used in libraries like Vuelidate where they use reactive() for setting up state for validations.
Opinions from Vue Community
The amazing Michael Thiessen wrote a brilliant in-depth article about this topic and collected the opinions of famous people in the Vue community.
Summarized, they all use ref
by default and use reactive
when they need to group things.
Conclusion
So, should you use ref
or reactive
?
My recommendation is to use ref
by default and reactive
when you need to group things. The Vue community has the same opinion but it's totally fine if you decide to use reactive
by default.
Both ref
and reactive
are powerful tools to create reactive variables in Vue 3. You can even use both of them without any technical drawbacks. Just pick the one you like and try to stay consistent in how you write your code!
If you liked this article, follow me on Twitter to get notified about new blog posts and more content from me.
Alternatively (or additionally), you can subscribe to my weekly Vue newsletter.
Use Shiki to Style Code Blocks in HTML Emails
I recently developed a custom newsletter service using Nuxt 3. One of the main reasons why I developed it on my own was that I wanted to use good-looking code blocks in my emails.
What's New in Vue 3.3
This article gives an overview of the highlighted features in Vue 3.3