Vue.js stands out from other frameworks for its intuitive reactivity. Vue 3 composition api is going to removing some limitations in Vue 2 and provide a more explicit api.
Quick Intro to the Composition API
There are two ways to create reactive "things":
reactive()-
ref()/computed()
Introducing reactive()
reactive(obj) will return a new object that looks exactly the same as obj, but any mutation to the new object will be tracked.
For example:
// template: {{ state.a }} - {{ state.b }}
const state = reactive({ a: 3 })
// renders: 3 - undefined
state.a = 5
state.b = 'bye'
// renders: 5 - bye
This works exactly like data in Vue 2. Except we can now add new properties to them as reactivity is implemented with proxies in Vue 3.
Introducing Ref
Vue composition API introduced Ref which is simply an object with 1 property .value. We can express this using Typescript:
interface Ref<A> {
value: A
}
There are two ways of creating refs:
-
ref()-
.valuecan be get/set.
-
-
computed()-
.valueis readonly unless a setter is provided.
-
For Example:
const countRef = ref(0) // { value: 0 }
const countPlusOneRef = computed(() => countRef.value + 1) // { value: 1 }
countRef.value = 5
/*
* countRef is { value: 5 }
* countPlusOneRef is { value: 6 } (readonly)
*/
reactive() is bad; Ref is good.
This section of the article is purely my tentative opinion on the composition api after building a few projects with it. Do try it yourself and let me know if you agree.
Before using the composition api, I thought reactive() would be the api that everyone will end up using as it doesn't require the need to do .value. Surprisingly, after building a few projects with the composition api, not once have I used reactive() so far!
Here are 3 reasons why:
- Convenience -
ref()allow declaration of new reactive variable on the fly. - Flexibility -
ref()allow complete replacement of an object - Explicitness -
.valueforces you to be aware of what you are doing
1. Convenience
The composition api is proposed to provide a way to group code with accordance to their feature in the component instead of their function in Vue. The options api groups code into data, computed, methods, lifecycles etc. This make it almost impossible to group code by feature. See the following image:
Consider the following examples:
const state = reactive({
count: 0,
errorMessage: null,
})
setTimeout(() => state.count++, 1000)
watch(state.count, count => {
if (count > 10) {
state.errorMessage = 'Larger than 10.'
}
})
If we use reactive() to store multiple properties. It is easy to fall back into the trap of grouping things by functions, not feature. You will likely be jumping around the code base to modify that reactive object. This makes the development process less smooth.
const count = ref(0)
setTimeout(() => count.value++, 1000)
const errorMessage = ref(null)
watch(count, count => {
if (count > 10) {
errorMessage.value = 'Larger than 10.'
}
})
On the other hand, ref() allow us to introduce new variables on the fly. From the example above, I only introduce variables as I need them. This makes the development process much smoother and intuitive.
2. Flexibility
I initially thought the sole purpose of ref() was to enable primitive values to be reactive. But it can become extremely handy too when using ref() with objects.
Consider:
const blogPosts = ref([])
blogPosts.value = await fetchBlogPosts()
If we wish to do the same with reactive, we need to mutate the array instead.
const blogPosts = reactive([])
for (const post of (await fetchBlogPosts())) {
blogPosts.push(post)
}
or with our "beloved" Array.prototype.splice()
const blogPosts = reactive([])
blogPosts.splice(0, 0, ...(await fetchBlogPosts()))
As illustrated, ref() is simpler to work with in this case as you can just replace the whole array with a new one. If that doesn't convince you, imagine if the blogPosts needs to be paginated:
watch(page, page => {
// remove everything from `blogPosts`
while (blogPosts.length > 0) {
blogPosts.pop()
}
// add everything from new page
for (const post of (await fetchBlogPostsOnPage(page))) {
blogPosts.push(post)
}
})
or with our best friend splice
watch(page, page => {
blogPosts.splice(0, blogPosts.length, ...(await fetchBlogPostsOnPage(page)))
})
But if we use ref()
watch(page, page => {
blogPosts.value = await fetchBlogPostsOnPage(page)
})
It is much flexible to work with.
3. Explicitness
reactive() returns an object that we will interact with the same we interact with other non-reactive object. This is cool, but can become confusing in practise if we have deal with other non-reactive objects.
watch(() => {
if (human.name === 'Jason') {
if (!partner.age) {
partner.age = 30
}
}
})
We cannot really tell if human or partner is reactive. But if we ditch using reactive() and consistently utilise ref(), we won't have the same problem.
.value may seem wordy at first; but it helps reminding us that we are dealing with reactivity.
watch(() => {
if (human.value.name === 'Jason') {
if (!partner.age) {
partner.age = 30
}
}
})
It becomes obvious now that human is reactive but not partner.
Conclusion
The above observations and opinions are totally tentative. What do you think? Do you agree ref() is going to dominate in Vue 3? Or do you think reactive() will be preferred?
Let me know in the comments! I would love to hear more thoughts!

Latest comments (27)
On the other hand, even if
reactiveis used,refcannot be avoided. For example,computedreturns aref.I prefer to use the same apiAppreciate the article. Definitely something I am attempting to work out in my own code as we speak.
Have you considered this way of working with "reactive"? In my opinion it's better than use .value but have all the best of "ref" kind of declaration
Yes. I actually proposed that to Evan before too. But after actually using the composition API, I was surprised to find ref much more natural to use. Having said that, using a
stateobject is totally valid pattern tho!this should work the same way as
ref()Exactly! So just use ref!
I guess I have a few questions about this.
1) Why can't you use
reactiveto declare multiple variables as you are doing withrefandcomputed. I'm not sure I see why that is an advantage unless I misunderstand the usage.2) Why, in your blog post example did you chose to declare a reactive array directly instead of
reactive({ blogPosts: [] })which would have made more sense and have largely the equivalent behaviour to usingref.The API RFC document seems to explain fairly well where
reactiveandrefshould and should not be used. I don't really see wherereactivecould really be dangerous outside of simply not understanding how it will behave relative torefandcomputed.vue-composition-api-rfc.netlify.co...
I appreciate your input, and Vue is definitely not holy in my eyes. It is a framework amongst many, your comment was simply off topic when the discussion is clearly about Vue3 Composition API, and not a debate about which X is better than Y. The agressivity in your reply, you can take elsewhere.
"I dont have comments on this post", "not interested in latest developments in Vue"
Then why are you commenting on this post, which is about Vue?
You are also not interested in changes to Vue, yet you are comparing it to another framework. It is hard enough to get professional environments to use Vuejs instead of React, without mixing other libraries into the mix. Good job, sir.
Neither reactive nor ref feel like a good solution to me. We know that the options API is very approchable by beginners, which makes Vue successful, so as a consequence the Vue team had to deal with a lot of beginners mistakes ; for the options API, it was often a misunderstanding of how the
thiskeyword works. Sothishas been flagged as a footgun and is avoided as much as possible in Vue 3 RFCs.Yet I feel like using
thisinsetupwould have solve so many trouble, while being much closer to the remaining options API:.valueor destructuring reactive objects ; the only trap is when the context changes, but:thisI believe the difficulty in composing logic while keeping the same execution context is overstated, nothing that can be fixed with a simple
.call(this). And again, this is a general JavaScript issue, nothing specific to Vue. Devs will meet the exact same issue on the logic/services parts of their app, if they mix OOP/FP style, which seems to be a current trend.Thanks for this article and for starting the enriching conversation below.
At least for me the resolution of the first image is such that I'm finding it hard to read. Do you think you could update that image to a higher res and/or link out to the src?
Hi.
The image is intended to illustrate the problem with option api. I took the image from the vue composition api rfc. See here.
What I love about this post is that you don't need to have learnt vue 3 to understand what's going on, in fact, I just learned something new here, important concepts rather. Thanks for this