ref and reactive
Vue gives you two APIs for creating reactive state: ref and reactive. Understanding the difference between them — and when to use each — is the foundation of working with Vue’s reactivity system.
ref wraps any value — primitive or object — in a reactive container:
import { ref } from 'vue'
const count = ref(0) // numberconst name = ref('Alice') // stringconst isActive = ref(true) // booleanconst person = ref({ name: 'Alice', age: 30 }) // objectTo read or write the value in <script setup>, access .value:
console.log(count.value) // 0count.value = 5 // triggers re-rendercount.value++ // also triggers re-render
console.log(person.value.name) // 'Alice'person.value.name = 'Bob' // triggers re-renderIn templates, Vue auto-unwraps refs — you don’t write .value:
<template> <p>{{ count }}</p> <!-- not count.value --> <p>{{ person.name }}</p> <!-- not person.value.name --></template>reactive
Section titled “reactive”reactive makes a plain object deeply reactive — all nested properties are reactive automatically:
import { reactive } from 'vue'
const person = reactive({ name: 'Alice', age: 30, address: { city: 'Portland' }})
person.name = 'Bob' // triggers re-renderperson.address.city = 'Seattle' // also triggers re-renderNo .value — you access properties directly, both in script and template.
<template> <p>{{ person.name }}</p> <p>{{ person.address.city }}</p></template>When to use ref vs reactive
Section titled “When to use ref vs reactive”The Vue team’s recommendation for most cases: prefer ref. Here’s why:
ref works for everything — primitives and objects. You always access it the same way (.value in script). reactive only works for objects and has a key limitation: you can’t destructure it without losing reactivity:
const state = reactive({ count: 0, name: 'Alice' })
// ❌ This breaks reactivity — count is now a plain numberconst { count } = state
// ✅ This is fine — you keep the reactive referencestate.count++ref doesn’t have this problem because the reference itself is reactive. In this course, FamilyTree’s Pinia store uses ref for all state.
Using ref in the Pinia store
Section titled “Using ref in the Pinia store”In FamilyTree’s familyStore.ts, all state is declared with ref:
const people = ref<Person[]>([])const focusPersonId = ref<string | null>(null)This works with storeToRefs (Module 06) to safely destructure store state while keeping reactivity.
Exercise
Section titled “Exercise”- Create a
PersonForm.vuewith areactiveform object:{ name: '', birthYear: '' }. - Bind each field to an input with
v-model(you’ll learn the full details in Module 04). - Display the current values using
{{ form.name }}and{{ form.birthYear }}. - Refactor to use
reffor each field instead. Compare the code — notice the.valueaccess pattern.
ref(value)wraps any value reactively; access with.valuein script, auto-unwrapped in templates.reactive(object)makes an object and all its properties deeply reactive; no.valueneeded.- Prefer
ref— it works for all types and avoids destructuring pitfalls. - Destructuring a
reactiveobject loses reactivity; usetoRefsor keep property access on the object.