Reactivity Fundamentals
API Preference
This page and many other chapters later in the guide contain different content for the Options API and the Composition API. Your current preference is Composition API . You can toggle between the API styles using the "API Preference" switches at the top of the left sidebar.
Declaring Reactive State
ref()
In Composition API, the recommended way to declare reactive state is using the
ref()
function:
ref()
takes the argument and returns it wrapped within a ref object with a
.value
property:
See also: Typing Refs
To access refs in a component's template, declare and return them from a component's
setup()
function:
Notice that we did
not
need to append
.value
when using the ref in the template. For convenience, refs are automatically unwrapped when used inside templates (with a few
caveats
).
You can also mutate a ref directly in event handlers:
For more complex logic, we can declare functions that mutate refs in the same scope and expose them as methods alongside the state:
Exposed methods can then be used as event handlers:
Here's the example live on Codepen , without using any build tools.
<script setup>
Manually exposing state and methods via
setup()
can be verbose. Luckily, it can be avoided when using
Single-File Components (SFCs)
. We can simplify the usage with
<script setup>
:
Top-level imports, variables and functions declared in
<script setup>
are automatically usable in the template of the same component. Think of the template as a JavaScript function declared in the same scope - it naturally has access to everything declared alongside it.
TIP
For the rest of the guide, we will be primarily using SFC +
<script setup>
syntax for the Composition API code examples, as that is the most common usage for Vue developers.
If you are not using SFC, you can still use Composition API with the
setup()
option.
Why Refs?
You might be wondering why we need refs with the
.value
instead of plain variables. To explain that, we will need to briefly discuss how Vue's reactivity system works.
When you use a ref in a template, and change the ref's value later, Vue automatically detects the change and updates the DOM accordingly. This is made possible with 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. Later on, when a ref is mutated, it will trigger a re-render for components that are tracking it.
In standard JavaScript, there is no way to detect the access or mutation of plain variables. However, we can intercept the get and set operations of an object's properties using getter and setter methods.
The
.value
property gives Vue the opportunity to detect when a ref has been accessed or mutated. Under the hood, Vue performs the tracking in its getter, and performs triggering in its setter. Conceptually, you can think of a ref as an object that looks like this:
Another nice trait of refs is that unlike plain variables, you can pass refs into functions while retaining access to the latest value and the reactivity connection. This is particularly useful when refactoring complex logic into reusable code.
The reactivity system is discussed in more details in the Reactivity in Depth section.
Deep Reactivity
Refs can hold any value type, including deeply nested objects, arrays, or JavaScript built-in data structures like
Map
.
A ref will make its value deeply reactive. This means you can expect changes to be detected even when you mutate nested objects or arrays:
Non-primitive values are turned into reactive proxies via
reactive()
, which is discussed below.
It is also possible to opt-out of deep reactivity with
shallow refs
. For shallow refs, only
.value
access is tracked for reactivity. Shallow refs can be used for optimizing performance by avoiding the observation cost of large objects, or in cases where the inner state is managed by an external library.
Further reading:
DOM Update Timing
When you mutate reactive state, the DOM is updated automatically. However, it should be noted that the DOM 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.
To wait for the DOM update to complete after a state change, you can use the nextTick() global API:
reactive()
There is another way to declare reactive state, with the
reactive()
API. Unlike a ref which wraps the inner value in a special object,
reactive()
makes an object itself reactive:
See also: Typing Reactive
Usage in template:
Reactive objects are JavaScript Proxies and behave just like normal objects. The difference is that Vue is able to intercept the access and mutation of all properties of a reactive object for reactivity tracking and triggering.
reactive()
converts the object deeply: nested objects are also wrapped with
reactive()
when accessed. It is also called by
ref()
internally when the ref value is an object. Similar to shallow refs, there is also the
shallowReactive()
API for opting-out of deep reactivity.
Reactive Proxy vs. Original
It is important to note that 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. Therefore, the best practice when working with Vue's reactivity system is to exclusively use the proxied versions of your state .
To ensure consistent access to the proxy, calling
reactive()
on the same object always returns the same proxy, and calling
reactive()
on an existing proxy also returns that same proxy:
This rule applies to nested objects as well. Due to deep reactivity, nested objects inside a reactive object are also proxies:
Limitations of
reactive()
The
reactive()
API has a few limitations:
-
Limited value types: it only works for object types (objects, arrays, and collection types such as
Map
andSet
). It cannot hold primitive types such asstring
,number
orboolean
. -
Cannot replace entire object: since Vue's reactivity tracking works over property access, we must always keep the same reference to the reactive object. This means we can't easily "replace" a reactive object because the reactivity connection to the first reference is lost:
-
Not destructure-friendly: when we destructure a reactive object's primitive type property into local variables, or when we pass that property into a function, we will lose the reactivity connection:
Due to these limitations, we recommend using
ref()
as the primary API for declaring reactive state.
Additional Ref Unwrapping Details
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:
If a new ref is assigned to a property linked to an existing ref, it will replace the old ref:
Ref unwrapping only happens when nested inside a deep reactive object. It does not apply when it is accessed as a property of a shallow reactive object .
Caveat in Arrays and Collections
Unlike reactive objects, there is
no
unwrapping performed when the ref is accessed as an element of a reactive array or a native collection type like
Map
:
Caveat when Unwrapping in Templates
Ref unwrapping in templates only applies if the ref is a top-level property in the template render context.
In the example below,
count
and
object
are top-level properties, but
object.id
is not:
Therefore, this expression works as expected:
...while this one does NOT :
The rendered result will be
[object Object]1
because
object.id
is not unwrapped when evaluating the expression and remains a ref object. To fix this, we can destructure
id
into a top-level property:
Now the render result will be
2
.
Another thing to note is that a ref does get unwrapped if it is the final evaluated value of a text interpolation (i.e. a
{{ }}
tag), so the following will render
1
:
This is just a convenience feature of text interpolation and is equivalent to
{{ object.id.value }}
.