Skip to main content
  1. Posts/

Understanding Vue 3 Reactivity: Refs vs Reactive

·4 mins
Author
Venkata Subash
Building fast, accessible web apps with Vue.js, Nuxt, React, and TypeScript. I write about what I learn.

Introduction
#

Vue 3’s reactivity system is powerful, but it can be confusing when you’re first learning about ref() and reactive(). Both are essential for creating reactive state, but they’re used in different situations. Let’s clarify when and how to use each one.

Reactivity Basics
#

At its core, Vue’s reactivity system uses JavaScript Proxies to detect changes to your data and update the DOM accordingly. When you modify a reactive value, Vue automatically re-renders any components that depend on it.

ref() - Wrapping Single Values
#

What is ref()?
#

ref() creates a reactive reference to a single value. It wraps the value in an object with a .value property.

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0
count.value = 1          // Triggers reactivity

When to Use ref()
#

Use ref() when you need to:

  • Store simple values (strings, numbers, booleans)
  • Have mutable primitive values
  • Create a reference that can be reassigned
import { ref } from 'vue'

export default {
  setup() {
    const username = ref('John')
    const isLoading = ref(false)
    const pageCount = ref(1)

    return { username, isLoading, pageCount }
  }
}

ref() in Templates
#

Interestingly, in templates you don’t need to use .value:

<template>
  <!-- Vue automatically unwraps ref in templates -->
  <p>{{ count }}</p>
  <!-- equivalent to {{ count.value }} -->
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

reactive() - For Objects
#

What is reactive()?
#

reactive() creates a reactive proxy for an object. It works directly with properties, no .value needed.

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello'
})

console.log(state.count)  // 0 (no .value!)
state.count = 1           // Triggers reactivity

When to Use reactive()
#

Use reactive() when you need to:

  • Create a reactive object with multiple properties
  • Model a complex entity with related fields
  • Maintain an object-like structure
import { reactive } from 'vue'

export default {
  setup() {
    const user = reactive({
      id: 1,
      name: 'Alice',
      email: 'alice@example.com',
      isAdmin: false
    })

    return { user }
  }
}

Head-to-Head Comparison
#

ref() vs reactive()
#

Featureref()reactive()
Works withAny typeObjects only
Property access.value requiredDirect property
Template unwrappingAutomaticN/A
ReassignabilityCan reassign entire valueCan reassign properties
TypeScript supportExcellent with genericsHarder to type

Practical Examples
#

Using ref() for Form Input
#

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="email" type="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />
    <button type="submit">Login</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'

const email = ref('')
const password = ref('')

const handleSubmit = () => {
  console.log('Logging in with:', {
    email: email.value,
    password: password.value
  })
}
</script>

Using reactive() for Complex State
#

<template>
  <div class="profile">
    <h1>{{ profile.name }}</h1>
    <p>{{ profile.email }}</p>
    <p>Role: {{ profile.role }}</p>
    <button @click="toggleAdmin">Toggle Admin</button>
  </div>
</template>

<script setup>
import { reactive } from 'vue'

const profile = reactive({
  name: 'Bob',
  email: 'bob@example.com',
  role: 'user',
  isAdmin: false
})

const toggleAdmin = () => {
  profile.isAdmin = !profile.isAdmin
  profile.role = profile.isAdmin ? 'admin' : 'user'
}
</script>

The Golden Rule
#

If you’re unsure which to use, here’s a simple rule:

Use ref() by default. It’s more predictable, easier to reassign, and has better TypeScript support. Use reactive() only when you specifically need an object-like interface.

Advanced: Unwrapping in Objects
#

When you return multiple ref() values from setup(), they remain as refs:

const state = reactive({
  count: ref(0),
  message: ref('Hello')
})

console.log(state.count)  // Automatically unwrapped! Returns 0, not Ref object
state.count++             // Works without .value

This is a special feature - objects created with reactive() automatically unwrap any ref() properties!

Best Practices
#

  1. Default to ref() for most reactive state
  2. Use reactive() for form models or related object data
  3. Combine them wisely - use refs in reactive objects when appropriate
  4. Be consistent in your codebase - pick a style and stick to it
  5. Document your reasoning if you use reactive() for readability

Conclusion
#

Both ref() and reactive() are essential tools in Vue 3. While ref() is more versatile and generally recommended, reactive() shines when modeling complex objects. Understanding the differences will help you write cleaner, more maintainable Vue code.

The key is using the right tool for the right job. Start with ref(), and introduce reactive() when your state naturally fits an object structure.