Options API: declare reactive state with data()
In Options API, use the `data` option to declare reactive state. The option value must be a function that returns an object. Vue calls this function when creating a new component instance and wraps the returned object in its reactivity system. Top-level properties of the returned object are proxied on the component instance (accessible via `this` in methods and lifecycle hooks).
Options API: data properties must be declared upfront
All data properties must be present in the object returned by the `data()` function when the component instance is first created. Use `null`, `undefined`, or placeholder values for properties that don't yet have their desired value. Properties added to `this` after creation will not trigger reactive updates.
Avoid $ and _ prefixes for data properties
Vue reserves the `$` prefix for exposing built-in APIs via the component instance and the `_` prefix for internal properties. Avoid using names starting with either of these characters for top-level `data` properties.
Vue 3 uses Proxies for reactivity; original object not modified
In Vue 3, data is made reactive using JavaScript Proxies. When you assign an object to reactive state, the reactive proxy is returned, not the original object. Accessing `this.someObject` after assigning it returns a reactive proxy of the original object. The original object is left intact and is not made reactive. Always access reactive state as a property of `this` to ensure you're working with the reactive proxy.
Composition API: ref() for reactive state
In Composition API, use `ref()` to declare reactive state. `ref()` takes a value and returns it wrapped in a ref object with a `.value` property. Access and mutate the wrapped value via the `.value` property in JavaScript, but refs are automatically unwrapped in templates for convenience.
ref() example with count
import { ref } from 'vue'
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Expose refs from setup() function
To access refs in a component's template, declare them in the `setup()` function and return them in a return object. The setup() function is a special hook dedicated for Composition API.
No .value needed in templates
Refs are automatically unwrapped when used in templates. You do not need to append `.value` when using a ref inside template interpolation or event handlers.
<script setup> simplifies state exposure
In Single-File Components, `<script setup>` allows you to avoid manually exposing state and methods via `setup()`. Top-level imports, variables, and functions declared in `<script setup>` are automatically usable in the template. The template has access to everything declared in the same scope.
<script setup> example
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Why refs use .value property
Vue's reactivity system uses dependency tracking. When a component renders for the first time, Vue tracks every ref used during rendering. When a ref is mutated later, it triggers a re-render for components tracking it. The `.value` property allows Vue to detect when a ref has been accessed or mutated via getter and setter methods. In standard JavaScript, there's no way to detect plain variable access or mutation, but Vue can intercept object property access and mutation.
Refs enable value passing to functions
Refs allow you to pass reactive state into functions while retaining access to the latest value and the reactivity connection. This is particularly useful when refactoring complex logic into reusable code.
Options API: declare methods with methods option
Add methods to a component instance using the `methods` option. This should be an object containing the desired methods. Methods can be called in lifecycle hooks or other methods.
Vue auto-binds this in methods
Vue automatically binds the `this` value for methods so it always refers to the component instance. This ensures methods retain the correct `this` value when used as event listeners or callbacks. Never use arrow functions when defining methods as that prevents Vue from binding the correct `this`.
Deep reactivity by default
In Vue, state is deeply reactive by default. Changes are detected even when mutating nested objects or arrays.
ref() makes values deeply reactive
A ref makes its value deeply reactive. This means changes are detected even when mutating nested objects or arrays inside the ref. Non-primitive values in refs are turned into reactive proxies via `reactive()` internally.
Shallow refs available for optimization
It is possible to opt-out of deep reactivity using shallow refs (via `shallowRef()`). With shallow refs, only `.value` access is tracked for reactivity. Shallow refs can optimize performance by avoiding the observation cost of large objects or when inner state is managed by external libraries.
DOM updates are not synchronous
When reactive state is mutated, DOM updates are not applied synchronously. Vue buffers DOM updates until the next tick in the update cycle to ensure each component updates only once regardless of how many state changes occur.
nextTick() waits for DOM update
Use the `nextTick()` global API to wait for the DOM update to complete after a state change. It returns a Promise that resolves when the DOM has been updated.
nextTick() example
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Now the DOM is updated
}
reactive() API creates reactive objects
The `reactive()` API makes an object itself reactive. Unlike `ref()` which wraps the inner value in a special object, `reactive()` converts the object to a reactive Proxy. Usage in templates does not require a `.value` property.
reactive() converts objects deeply
`reactive()` converts objects deeply by wrapping nested objects with `reactive()` when they are accessed. It is also called by `ref()` internally when the ref value is an object.
reactive() returns proxy, not original object
The value returned by `reactive()` is a Proxy of the original object, not equal to the original object. Mutating the original object will not trigger updates. Only the proxy is reactive. Best practice is to exclusively use the proxied version of the state.
reactive() returns same proxy for same object
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() nested objects are also proxies
Due to deep reactivity, nested objects inside a reactive object are also proxies. If you assign a raw object to a property of a reactive object, it is converted to a proxy.
reactive() limitations: value types
`reactive()` only works with object types: plain objects, arrays, and collection types like `Map` and `Set`. It cannot hold primitive types such as `string`, `number`, or `boolean`. Use `ref()` for primitive values.
reactive() cannot replace entire object
Cannot reassign a reactive object to a new object because Vue's reactivity tracking works over property access. Replacing a reactive object loses the reactivity connection to the original reference. Always mutate properties within a reactive object rather than replacing the object itself.
reactive() destructuring loses reactivity
Destructuring a reactive object's property into a local variable loses the reactivity connection. Passing a destructured primitive property to a function also loses reactivity. To retain reactivity, pass the entire reactive object into functions.
Prefer ref() over reactive()
Due to its limitations, `ref()` is recommended as the primary API for declaring reactive state in Composition API.
Ref auto-unwrapping in reactive objects
A ref is automatically unwrapped when accessed or mutated as a property of a reactive object. It behaves like a normal property without needing `.value`. If a new ref is assigned to a property linked to an existing ref, it replaces the old ref and the original ref is disconnected.
Ref unwrapping only in deep reactive objects
Ref unwrapping only happens when nested inside a deep reactive object. It does not apply when accessed as a property of a shallow reactive object.
No ref unwrapping in arrays or collections
Unlike reactive objects, refs are not automatically unwrapped when accessed as an element of a reactive array or native collection type like `Map`. You must use `.value` to access the ref value in these cases.
Ref unwrapping only for top-level template properties
Ref unwrapping in templates only applies if the ref is a top-level property in the template render context. Nested ref properties like `object.id` are not unwrapped. However, text interpolation ({{ }}) does unwrap refs as a convenience feature.
Text interpolation unwraps refs
A ref gets unwrapped if it is the final evaluated value of a text interpolation ({{ }} tag). So {{ object.id }} will render the unwrapped value. This is equivalent to {{ object.id.value }}.
Stateful methods problem in component reuse
When dynamically creating stateful method functions (like debounced handlers), sharing the same function across multiple component instances causes interference because the function maintains internal state. Each instance should have its own copy of the function.
Create stateful methods in created hook
For Options API, create stateful methods like debounced handlers in the `created` lifecycle hook so each component instance gets its own copy of the function. Also clean up the handler in the `unmounted` hook (e.g., calling `.cancel()` on a debounced function).
Reactivity Transform removed from Vue 3.4
Reactivity Transform was an experimental feature that has been removed in Vue 3.4. If you still intend to use it, it is now available via the Vue Macros plugin at https://vue-macros.sxzz.moe/features/reactivity-transform.html.
$ref macro for reactive variables
The $ref() macro is a compile-time transform that treats a variable as reactive. It is not an actual runtime method. Variables declared with $ref() can be accessed and re-assigned like normal variables, but operations are compiled into refs with .value. For example, let count = $ref(0) compiles to let count = ref(0) with automatic .value handling.
All reactivity API macros with $ prefix
Every reactivity API that returns refs has a $-prefixed macro equivalent. These include: ref -> $ref, computed -> $computed, shallowRef -> $shallowRef, customRef -> $customRef, toRef -> $toRef. These macros are globally available and do not need to be imported when Reactivity Transform is enabled, but can optionally be imported from vue/macros.
$() macro for destructuring reactive objects
The $() macro allows destructuring refs and reactive objects while preserving reactivity. It works on composition functions that return an object of refs. If the destructured value is already a ref, it is returned as-is. If it is not a ref, it is wrapped in a ref. The $() macro works on both reactive objects and plain objects containing refs.
$() macro converts refs to reactive variables
The $() macro can be used to convert existing refs into reactive variables when the Vue compiler cannot determine ahead of time that a function will return a ref. For example: let count = $(myCreateRef()) where myCreateRef() returns a ref.
$$() escape macro for function boundaries
The $$() macro serves as an escape hint to prevent .value from being appended to reactive variables. It is used in two cases: (1) when passing reactive variables as arguments to functions expecting refs, and (2) when returning reactive variables inside function scope to retain reactivity in the returned object.
ref type inference in TypeScript
When using ref() in TypeScript, the type is inferred from the initial value. For example, `const year = ref(2020)` results in inferred type `Ref<number>`. Assigning a different type to the value later results in a TypeScript error.
Explicit ref type annotation
To specify a complex type for a ref's inner value, use the Ref type: `const year: Ref<string | number> = ref('2020')`. Alternatively, pass a generic argument when calling ref() to override default inference: `const year = ref<string | number>('2020')`.
ref with generic type but no initial value
If you specify a generic type argument for ref() but omit the initial value, the resulting type will be a union type that includes `undefined`. For example, `const n = ref<number>()` results in inferred type `Ref<number | undefined>`.
reactive type inference in TypeScript
reactive() implicitly infers the type from its argument. For example, `const book = reactive({ title: 'Vue 3 Guide' })` infers type `{ title: string }`. To explicitly type a reactive property, use interfaces.
Do not use generic argument for reactive()
It is not recommended to use the generic argument of reactive() because the returned type, which handles nested ref unwrapping, is different from the generic argument type.
useTemplateRef automatic type inference
With Vue 3.5 and @vue/language-tools 2.1, the type of refs created by useTemplateRef() in SFCs can be automatically inferred for static refs based on what element or component the matching ref attribute is used on. In cases where auto-inference is not possible, cast the template ref to an explicit type via the generic argument.
useTemplateRef explicit type casting
When auto-inference is not possible, explicitly type template refs: `const el = useTemplateRef<HTMLInputElement>('el')`.
Template ref type safety considerations
For strict type safety with template refs, use optional chaining or type guards when accessing el.value, because the initial ref value is null until the component is mounted, and it can also be set to null if the referenced element is unmounted by v-if.
Template refs before Vue 3.5
Before Vue 3.5, template refs must be created with an explicit generic type argument and an initial value of null: `const el = ref<HTMLInputElement | null>(null)`. This allows accessing el.value only after the component is mounted.
Reactivity definition and paradigm
Reactivity is a programming paradigm that allows adjustment to changes in a declarative manner. An Excel spreadsheet is the canonical example: when a cell formula's dependencies change, the cell value updates automatically. This contrasts with JavaScript, where variables do not automatically update when their dependencies change.
Reactive effects and dependencies
A side effect (or effect) is code that modifies program state. Dependencies are the values used to perform the effect; the effect is a subscriber to its dependencies. A reactive effect system must: (1) track when a variable is read, (2) make the running effect a subscriber to read variables, and (3) detect when a variable is mutated and notify all subscriber effects to re-run.
Vue's reactivity implementation with Proxies and getters/setters
Vue 3 uses Proxies to intercept property access on reactive objects and getter/setters for refs. Proxies intercept both reading and writing of object properties. When a property is read, track() is called to register the current active effect as a subscriber. When a property is written, trigger() is called to notify all subscriber effects to re-run.
Pseudo-code for Vue's reactive() function
The reactive() function wraps an object in a Proxy that calls track() on property reads and trigger() on property writes. The Proxy's get trap calls track(target, key) and returns target[key]. The set trap assigns target[key] = value and calls trigger(target, key).
Pseudo-code for Vue's ref() function
The ref() function returns an object with a get value() accessor that calls track() and returns the wrapped value, and a set value(newValue) accessor that assigns the new value and calls trigger(). This uses JavaScript getter/setter syntax rather than Proxies.
Limitation: reactive object proxy identity differs from original
The returned proxy from reactive() behaves like the original object but has a different identity when compared using the === operator, because it is a different object.
Limitation: destructuring reactive properties breaks reactivity
When you assign or destructure a reactive object's property to a local variable, accessing or assigning to that variable is non-reactive because it no longer triggers the get/set proxy traps on the source object. This 'disconnect' only affects the variable binding; if the variable points to a non-primitive value like an object, mutating that object would still be reactive.
track() function records active effects as subscribers
Inside track(target, key), if there is a currently running effect (activeEffect), the function retrieves the Set of subscriber effects for that property and adds the current effect to the Set. Effect subscriptions are stored in a global WeakMap<target, Map<key, Set<effect>>> data structure. If no subscribing effects Set existed, it is created on first track.
trigger() function runs all subscriber effects
Inside trigger(target, key), the function retrieves the subscriber effects for the property and invokes each one. This causes all effects that depend on this property to re-run.