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; usetoRefs(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 changesRemember
ref()for any value, accessed via.valuein script, unwrapped in templatesreactive()proxies an object; don't reassign or destructure it- Updates are batched;
await nextTick()to see the new DOM
Watch out for
- Forgetting
.valuein script:if (count > 3)compares the ref object, not the number. - Destructuring a reactive object or props and expecting the copies to stay reactive.