ref()
Use
method to create reactive objects that contain the values for the components state. When used inside script tags, the ref()
.value
property must be accessed to read and mutate. When used in template you can use the reference directly (it is automatically unwrapped).
<script setup> import { ref } from 'vue' const count = ref(0) const increment = () => { count.value++ } </script>
<template> <button @click="count++"> {{ count }} </button> <button @click="increment"> {{ count }} </button> </template>
reactive()
Use reactive()
to create references to objects, arrays, and collections (like Map
and Set
). It is deeply reactive by nature, and it avoids the use of .value
, because the object itself is made reactive. Also, with the use of toRefs()
it is possible to destructure the object properties and still keeping the reactivity while modifying either the reactive object or the destructured refs.
<script setup> import { reactive, toRefs } from 'vue' const state = reactive({ count: 0 }) // Destructuring while keeping reactivity const state = reactive({ a: 1, b: 2 }); const { a, b } = toRefs(state); </script>
<template> <button @click="state.count++"> {{ state.count }} </button> </template>
Reactivity Depth
Objects created with ref()
and reactive()
are deeply reactive by nature, this means, when some nested property is modified the change is detected reactively in all dependencies. If you want to avoid this behavior you can opt for using shallowRef()
or shallowReactive()
, which triggers changes only when the main .value
is modified (ref) or the complete object is replaced (reactive).
import { ref } from 'vue' const refObj = ref({ nested: { count: 0 }, arr: ['foo', 'bar'] }) function mutateRef() { // these will work as expected refObj.value.nested.count++ refObj.value.arr.push('baz') } const shallowRefObj = shallowRef({ nested: { count: 0 }, arr: ['foo', 'bar'] }) function mutateShallowRef() { // this won't trigger change shallowRefObj.value.nested.count++ // this will work as expected shallowRefObj.value = { nested: { count: 1 }, arr: ['foo', 'bar', 'baz'] } }
Considerations
When an object is used to create a reactive object, both are not equal.
const raw = {} const proxy = reactive(raw) // Proxy is NOT equal to the original. console.log(proxy === raw) // false // Calling reactive() on the same object returns the same proxy console.log(reactive(raw) === proxy) // true // Calling reactive() on a proxy returns itself console.log(reactive(proxy) === proxy) // true // This rule applies to nested objects as well const proxy2 = reactive({}) const raw2 = {} proxy2.nested = raw2 console.log(proxy2.nested === raw2) // false
When the reactive object value is entirely replaced, the reactivity is lost.
let state = reactive({ count: 0 }) // Reactivity lost! state = reactive({ count: 1 })
When a ref is part of a collection, the values is accessed through .value
.
const books = reactive([ref('Vue 3 Guide')]) // Need .value here console.log(books[0].value) const map = reactive(new Map([['count', ref(0)]])) // Need .value here console.log(map.get('count').value)
When refs are used in template expressions, the unwrapping of a ref property is made with .value
, and not accessing directly the property as in reactive()
objects.
const count = ref(0) const object = { id: ref(1) } // Unwrapped correctly {{ count + 1 }} // Unwrapped correctly {{ object.id.value + 1 }}
DOM Update Timing
DOM updates are performed asynchronously, and some times the access to a state that has not been updated can cause some errors. In order to wait all DOM updates, you can use nextTick()
.
import { nextTick } from 'vue' async function increment() { count.value++ await nextTick() // Now the DOM is updated }
References
- https://vuejs.org/guide/essentials/reactivity-fundamentals.html
- https://vuejs.org/api/reactivity-advanced.html#shallowref