LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2219 min read

reactive() and ref()

Understand exactly how Vue's two core reactivity primitives work, and when to reach for each.

How Vue's Reactivity Works

Vue 3's reactivity system is built on native JavaScript Proxies. When a reactive value is read inside a template or a computed/watcher, Vue records that dependency; when the value later changes, Vue knows precisely which parts of the UI depend on it and re-renders only those.

ref()

ref() wraps a value — of any type, including primitives — in a reactive object with a single .value property. You've been using it since the very first lesson.

import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
Why .value?

JavaScript can't make a plain primitive (a number, a string) itself reactive — ref() wraps it in an object so Vue has something it can intercept reads and writes on. Templates automatically unwrap .value for you, which is why you write {{ count }}, not {{ count.value }}, in a template.

reactive()

reactive() wraps an object (not a primitive) in a Proxy directly — no .value needed, since the object's own properties are what become reactive.

import { reactive } from 'vue';
const user = reactive({ name: 'Ada', age: 28 });
console.log(user.name); // 'Ada' — no .value needed
user.age++;
console.log(user.age); // 29

ref vs reactive: Which to Use

ref()reactive()
Works with primitives?YesNo — objects/arrays only
Access syntax.value in <script>, unwrapped in templatesDirect property access, everywhere
Can be reassigned entirely?Yes — myRef.value = newObjectNo — reassigning loses reactivity; must mutate properties
Common recommendationDefault choice for most valuesFine for a single, cohesive object that's never reassigned wholesale
A Common, Pragmatic Convention

Many Vue codebases default to ref() for everything, even objects, purely for consistency — its ability to be reassigned wholesale (myRef.value = newState) is also genuinely useful and something reactive() can't do.

The Destructuring Gotcha

Destructuring a reactive() object's properties breaks their reactive connection — you get a plain, disconnected value instead.

const user = reactive({ name: 'Ada', age: 28 });
// ❌ Breaks reactivity — `age` is now just a disconnected number
const { age } = user;
// ✅ Use toRefs() to destructure while keeping the reactive connection
import { toRefs } from 'vue';
const { age: reactiveAge } = toRefs(user);

Common Beginner Mistakes

Forgetting .value in <script> code

Unwrapping only happens automatically in templates — inside <script>, always access and assign through .value on a ref.

Reassigning a reactive() variable entirely

user = { name: 'New' } replaces the Proxy with a plain object, losing reactivity — mutate individual properties (user.name = 'New') instead, or use a ref if whole-object reassignment is needed.

FAQs

Yes — ref() actually wraps object values with reactive() internally, so ref({ name: 'Ada' }).value.name is fully reactive as well.

Because JavaScript has no way to intercept reads/writes on a plain variable — wrapping the value in an object with a .value property is what makes interception (and therefore reactivity) possible.

Summary

ref() and reactive() are both built on the same Proxy-based system, with ref() being the more flexible default for most values. Next, you'll look closer at <script setup> itself and the compiler macros it provides.

Next Lesson →

<script setup>