Reactive state: ref and reactive

Vue · Essentials · 12 min

Reactive state is what makes the UI update itself; misunderstanding ref vs reactive causes most beginner bugs.

Vue re-renders a component when reactive state it used changes. You create that state with two functions.

`ref(value)` wraps any value (number, string, object, array) in an object with a .value property. In <script> you read and write count.value; in the template refs are unwrapped automatically, so you write {{ count }}. Objects inside a ref are made deeply reactive too, so todos.value.push(x) triggers updates.

`reactive(object)` returns a Proxy of the object itself, with no .value: state.count++. It has limits:

  • It only works with objects, arrays, Maps and Sets, not primitives.
  • You cannot replace the whole object (state = reactive({...}) disconnects the template from the old one).
  • Destructuring (const { count } = state) copies plain values and loses reactivity; use toRefs(state) if you need that.

Because of these limits the Vue docs recommend `ref` as the default way to declare state, and many teams use ref exclusively.

Updates are batched: changing state does not re-render synchronously. Vue queues one update per component per tick. If you need the updated DOM right after a change, await nextTick().

A ref nested in a reactive object is unwrapped on access, but refs inside arrays and Maps are not.

Example

import { ref, reactive, toRefs, nextTick } from 'vue'

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

const todos = ref([])
todos.value.push({ text: 'learn Vue' })   // deep: this is reactive too

const state = reactive({ user: 'Ada', visits: 1 })
state.visits++                      // no .value with reactive

const { visits } = state            // plain number, NOT reactive
const { user } = toRefs(state)      // user is a ref linked to state.user
state.user = 'Grace'
console.log(visits, user.value)     // 2 'Grace'

const box = reactive({ n: ref(1) })
console.log(box.n)                  // 1 (ref unwrapped inside reactive)

await nextTick()                    // DOM now reflects the changes

Remember

  • ref() for any value, accessed via .value in script, unwrapped in templates
  • reactive() proxies an object; don't reassign or destructure it
  • Updates are batched; await nextTick() to see the new DOM

Watch out for

  • Forgetting .value in script: if (count > 3) compares the ref object, not the number.
  • Destructuring a reactive object or props and expecting the copies to stay reactive.

Check yourself

  1. `const state = reactive({ n: 1 }); let { n } = state; state.n = 5;` What is `n` now?
  2. Why do the Vue docs recommend ref() as the primary API?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vuejs.org/guide/essentials/reactivity-fundamentals.html