LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 2219 min read

reactive() and ref()

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

On this page 8 sections

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>