patternjavascriptvueModerate
Vue 3 Composition API: ref vs reactive — which to use
Viewed 0 times
Vue 3.0+
refreactivecomposition apivue3 statereactivitytoRefs
Problem
Developers are unsure whether to use ref() or reactive() for state in Vue 3. Using reactive() for primitives silently wraps them incorrectly; using ref() for objects requires .value everywhere.
Solution
Use ref() for primitives and when you need to reassign the entire object. Use reactive() for complex objects that are never fully replaced:
import { ref, reactive } from 'vue';
// ref: primitives and reassignable values
const count = ref(0);
const user = ref(null);
count.value++; // access with .value
user.value = fetchedUser; // can reassign entirely
// reactive: complex state objects
const form = reactive({
name: '',
email: '',
age: 0
});
form.name = 'Alice'; // no .value needed
// form = anotherObj; // WRONG: loses reactivity
// In templates, .value is auto-unwrapped
// <p>{{ count }}</p> — no .value needed in template
// <p>{{ user?.name }}</p>
// Pattern: prefer ref for everything in <script setup>
// to be consistent and avoid surprises
const items = ref([]);
const loading = ref(false);
const error = ref(null);
import { ref, reactive } from 'vue';
// ref: primitives and reassignable values
const count = ref(0);
const user = ref(null);
count.value++; // access with .value
user.value = fetchedUser; // can reassign entirely
// reactive: complex state objects
const form = reactive({
name: '',
email: '',
age: 0
});
form.name = 'Alice'; // no .value needed
// form = anotherObj; // WRONG: loses reactivity
// In templates, .value is auto-unwrapped
// <p>{{ count }}</p> — no .value needed in template
// <p>{{ user?.name }}</p>
// Pattern: prefer ref for everything in <script setup>
// to be consistent and avoid surprises
const items = ref([]);
const loading = ref(false);
const error = ref(null);
Why
ref() boxes a value into a reactive container ({value: ...}). reactive() uses a Proxy over the entire object. Reassigning a reactive() variable (form = newObj) breaks the reactive link because other code still holds the old Proxy reference. ref() avoids this because the container itself never changes.
Gotchas
- reactive() loses reactivity if you destructure it — use toRefs() to destructure safely
- ref() objects are auto-unwrapped inside reactive() — no double .value needed
- In templates, ref is auto-unwrapped at the top level — .value is NOT needed
- shallowRef() and shallowReactive() skip deep reactivity for performance
Code Snippets
Safe destructuring of reactive objects
// Destructuring reactive — WRONG (loses reactivity)
const { name } = form;
// Destructuring reactive — CORRECT
import { toRefs } from 'vue';
const { name, email } = toRefs(form);
// name.value, email.value are now reactive refsContext
When choosing how to declare reactive state in Vue 3 Composition API
Revisions (0)
No revisions yet.