If you have used the Composition API in Vue 3, you have probably come across the ref and reactive functions. These are used to create reactive data in Vue 3. reactive only works with objects, while ref can be used with any type of data.
Eduardo San Martin Morote
April 8, 2024
If you have used the Composition API in Vue 3, you have probably come across the ref
and reactive
functions. These are used to create reactive data in Vue 3. reactive
only works with objects, while ref
can be used with any type of data. Also, you cannot reassign a new value to a reactive
object, but you can with a ref
:
const user = reactive({
id: 1,
name: 'John Doe',
avatar: '<https://example.com/avatar.jpg>',
})
user = {
/* ... */
} // TypeError
const user = ref({
id: 1,
name: 'John Doe',
avatar: '<https://example.com/avatar.jpg>',
})
user.value = {
/* ... */
} // Works
But really, the main difference people see is in the usage:
ref
, you access the value with .value
, which might feel a bit annoying at firstreactive
, you can access the properties directlyThis is very noticeable when working with collections like arrays, Set, Maps, etc:
const users = ref([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])
// 👇
users.value.push({ id: 3, name: 'Foo Bar' })
vs
const users = reactive([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])
// ✨
users.push({ id: 3, name: 'Foo Bar' })
The truth is we shouldn't be picking one over the other based on this but based on how the data is modified. If the data is going to be replaced as a whole, it should be a ref
(or even a [shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref)
for performance reasons).
Personally, I really like using reactive
for JS collections like Sets and Maps because they all include a convenient .clear()
method to reset them!
So what does this have to do with stores? Well, when creating Setup Stores, you can choose to use ref
or reactive
to create state but reactive
can be problematic. Let me show you why.
export const useUsersStore = defineStore('users', () => {
const userList = reactive<User[]>([])
function addUser(user: User) {
userList.push(user)
}
return { userList, addUser }
})
We are using userList to conveniently store a list of users. Any modification within the store works perfectly but if we try to replace the userList
outside the store, something weird will happen...
const usersStore = useUsersStore()
// naively trying to replace the userList
usersStore.userList = []
userList
will no longer be reactive 🥲. You can try this yourself here.
The reason this doesn't work is because the userList
within the store is no longer the same as the userList
outside the store. So addUser()
is modifying a reactive array but we are not using it with usersStore.userList
. The reason behind this is very simple! Pinia stores are wrapped with reactive()
to remove the need of writing .value
everywhere.
In fact, we can even see the same issue with just Vue!
const userList = reactive([])
function addUser(user) {
// this only modifies the reactive array
userList.push(user)
}
const usersStore = reactive({ userList })
function reset() {
// this replaces the usersStore.userList with a new array
usersStore.userList = []
}
This is why I recommend using ref
(and shallowRef
) in stores as much as possible. It's the most flexible choice. I still find myself using reactive
and shallowReactive
for Sets and Maps.
In short, use reactive
(and shallowReactive
) only if you don't replace the data as a whole. Otherwise, use ref
(and shallowRef
).
Get a free lesson delivered to your inbox, just click on the button below.