isRef() function
isRef() checks if a value is a ref object. It takes a parameter of type Ref<T> | unknown and returns a type predicate r is Ref<T>. The return type is a type predicate, which means isRef can be used as a type guard to narrow types in conditionals.
unref() function
unref() returns the inner value if the argument is a ref, otherwise returns the argument itself. It is a sugar function equivalent to val = isRef(val) ? val.value : val. The type signature is function unref<T>(ref: T | Ref<T>): T.
toRef() normalization (3.3+)
toRef() can be used to normalize values, refs, and getters into refs starting in version 3.3+. The normalization signature has the type function toRef<T>(value: T): T extends () => infer R ? Readonly<Ref<R>> : T extends Ref ? T : Ref<UnwrapRef<T>>. It returns existing refs as-is, creates a readonly ref that calls the getter on .value access when passed a function, and creates normal refs from non-function values.
toRef() object property signature
toRef() can create a ref for a property on a source reactive object with the signature function toRef<T extends object, K extends keyof T>(object: T, key: K, defaultValue?: T[K]): ToRef<T[K]>. The created ref is synced with its source property: mutating the source property updates the ref, and mutating the ref updates the original property. This is different from ref(state.foo) which receives a plain value and is not synced.
toRef() with component props
toRef() is useful when you want to pass the ref of a prop to a composable function. When toRef is used with component props, the usual restrictions around mutating props still apply. Attempting to assign a new value to the ref is equivalent to trying to modify the prop directly and is not allowed. In such cases, consider using computed with get and set instead.
toRef() with optional properties
When using the object property signature, toRef() will return a usable ref even if the source property doesn't currently exist. This makes it possible to work with optional properties, which wouldn't be picked up by toRefs().
toValue() function (3.3+)
toValue() is only supported in version 3.3+. It normalizes values, refs, and getters to values. It is similar to unref() except that it also normalizes getters. If the argument is a getter, it will be invoked and its return value will be returned. The type signature is function toValue<T>(source: T | Ref<T> | (() => T)): T.
toValue() example
toValue(1) returns 1. toValue(ref(1)) returns 1. toValue(() => 1) returns 1.
toValue() in composables
toValue() can be used in composables to normalize an argument that can be either a value, a ref, or a getter. With the type MaybeRefOrGetter<number>, a composable can accept useFeature(1), useFeature(ref(1)), or useFeature(() => 1).
toRefs() function
toRefs() converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object. Each individual ref is created using toRef(). The type signature is function toRefs<T extends object>(object: T): { [K in keyof T]: ToRef<T[K]> } where ToRef = T extends Ref ? T : Ref<T>.
toRefs() creates linked refs
In toRefs(), each ref and the original property are linked. Mutating the original property updates the ref's value, and mutating the ref's value updates the original property.
toRefs() use case in composables
toRefs() is useful when returning a reactive object from a composable function so that the consuming component can destructure/spread the returned object without losing reactivity.
toRefs() enumerable properties only
toRefs() will only generate refs for properties that are enumerable on the source object at call time. To create a ref for a property that may not exist yet, use toRef() instead.
isProxy() function
isProxy() checks if an object is a proxy created by reactive(), readonly(), shallowReactive(), or shallowReadonly(). The type signature is function isProxy(value: any): boolean.
isReactive() function
isReactive() checks if an object is a proxy created by reactive() or shallowReactive(). The type signature is function isReactive(value: unknown): boolean.
isReadonly() function
isReadonly() checks whether the passed value is a readonly object. The properties of a readonly object can change, but they cannot be assigned directly via the passed object. The proxies created by readonly() and shallowReadonly() are both considered readonly, as is a computed() ref without a set function. The type signature is function isReadonly(value: unknown): boolean.
isShallow() function
isShallow() checks if an object is a proxy created by shallowRef, shallowReactive(), or shallowReadonly(). The type signature is function isShallow(value: unknown): boolean.
Reactive proxy vs original object in Vue 3
In Vue 3, data is made reactive by leveraging JavaScript Proxies. When you access `this.someObject` after assigning it, the value is a reactive proxy of the original object. Unlike in Vue 2, the original object is left intact and will not be made reactive. Always access reactive state as a property of `this`.
Why refs need .value property
Refs use the `.value` property to enable Vue's reactivity system to track and trigger updates. JavaScript has no native way to detect access or mutation of plain variables. Vue intercepts get and set operations on the `.value` property using getter and setter methods. The `.value` property gives Vue the opportunity to track when a ref has been accessed (in the getter) and when it has been mutated (in the setter).
How Vue tracks and triggers ref updates
Vue uses a dependency-tracking based reactivity system. When a component is rendered for the first time, Vue tracks every ref that was used during the render. When a ref is mutated later, it triggers a re-render for components that are tracking it. This mechanism works because Vue can intercept property access and mutation through the `.value` getter and setter.
Ref pseudo-code illustrating tracking and triggering
// pseudo code, not actual implementation
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Refs enable passing reactive state into functions
Unlike plain variables, refs can be passed into functions while retaining access to the latest value and the reactivity connection. This is particularly useful when refactoring complex logic into reusable code.
Deep reactivity in Options API
In Vue, state is deeply reactive by default. Changes are detected even when you mutate nested objects or arrays. You can mutate nested properties and array elements and Vue will track these changes.
Deep reactivity example in Options API
export default {
data() {
return {
obj: {
nested: { count: 0 },
arr: ['foo', 'bar']
}
}
},
methods: {
mutateDeeply() {
// these will work as expected.
this.obj.nested.count++
this.obj.arr.push('baz')
}
}
}
Refs support deeply nested objects and arrays
Refs can hold any value type, including deeply nested objects, arrays, or JavaScript built-in data structures like `Map`. A ref makes its value deeply reactive, so changes are detected even when you mutate nested objects or arrays.
Deep reactivity example with ref in Composition API
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// these will work as expected.
obj.value.nested.count++
obj.value.arr.push('baz')
}
DOM updates are asynchronous
When you mutate reactive state, the DOM is updated automatically, but the updates are not applied synchronously. Instead, Vue buffers them until the next tick in the update cycle to ensure that each component updates only once no matter how many state changes you have made.
Use nextTick() to wait for DOM updates
To wait for the DOM update to complete after a state change, use the `nextTick()` global API. This is an async function that returns a promise that resolves after Vue has flushed pending DOM updates.
nextTick() example in Composition API
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Now the DOM is updated
}
nextTick() example in Options API
import { nextTick } from 'vue'
export default {
methods: {
async increment() {
this.count++
await nextTick()
// Now the DOM is updated
}
}
}
reactive() returns a proxy not equal to original
The returned value from `reactive()` is a Proxy of the original object, which is not equal to the original object. Only the proxy is reactive - mutating the original object will not trigger updates. Always use the proxied version of your state.
reactive() proxy equality example
const raw = {}
const proxy = reactive(raw)
// proxy is NOT equal to the original.
console.log(proxy === raw) // false
reactive() returns same proxy consistently
Calling `reactive()` on the same object always returns the same proxy. Calling `reactive()` on an existing proxy returns that same proxy. This rule applies to nested objects as well.
reactive() consistency example
const raw = {}
const proxy = reactive(raw)
// 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
Nested objects in reactive() are also proxies
Due to deep reactivity, nested objects inside a reactive object are also proxies. When you assign a nested object, it becomes a proxy.
Nested objects in reactive() proxy example
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Ref unwrapping as reactive object property
A ref is automatically unwrapped when accessed or mutated as a property of a reactive object. In other words, it behaves like a normal property.
Ref unwrapping in reactive object example
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Assigning new ref to reactive object property
If a new ref is assigned to a property linked to an existing ref, it will replace the old ref and disconnect the original ref from the reactive object.
Replacing ref in reactive object example
const count = ref(0)
const otherCount = ref(2)
const state = reactive({ count })
state.count = otherCount
console.log(state.count) // 2
// original ref is now disconnected from state.count
console.log(count.value) // 0
Ref unwrapping only in deep reactive objects
Ref unwrapping only happens when nested inside a deep reactive object. It does not apply when a ref is accessed as a property of a shallow reactive object.
No ref unwrapping in arrays or collections
Unlike reactive objects, there is no unwrapping performed when a ref is accessed as an element of a reactive array or a native collection type like `Map`. You must use `.value` to access the ref's value.
Ref in reactive array example
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)
Ref unwrapping only applies to top-level template properties
Ref unwrapping in templates only applies if the ref is a top-level property in the template render context. Nested properties like `object.id` where `object` is a top-level property but `id` is not will not be unwrapped.
Ref unwrapping caveat in templates example
const count = ref(0)
const object = { id: ref(1) }
// This works because count is top-level:
// {{ count + 1 }}
// This does NOT work because object.id is not top-level:
// {{ object.id + 1 }}
// The rendered result will be `[object Object]1`
// To fix, destructure id into a top-level property:
const { id } = object
// Now {{ id + 1 }} renders `2`
Ref unwrapped in text interpolation as final value
A ref does get unwrapped if it is the final evaluated value of a text interpolation (i.e. a `{{ }}` tag). So `{{ object.id }}` will render the unwrapped value, which is equivalent to `{{ object.id.value }}`.