Vue 3 gives you two ways to make something reactive: ref() and reactive(). The docs explain the difference clearly enough. What they don't tell you is which one to reach for by default.
The short answer: ref(), almost always. reactive() for specific situations where you know what you're trading away.
Here's the problem with reactive(). It loses reactivity when you destructure it or reassign the root object, which catches people off guard more than it should.
const state = reactive({ count: 0, name: '' });
state.count++; // works, Vue tracks this
const { count } = state;
count++; // doesn't work, Vue has no idea
ref() sidesteps this because the reactive pointer lives on the .value property. You can pass a ref anywhere, destructure the container, and the ref itself stays tracked.
The case where reactive() actually earns its place: a form object where all the values are always read and written together, passed as-is to a composable, and you're never going to replace the whole reference. Some people find dropping the .value noise worth that constraint. I get it. But in composables especially, ref() is much easier to work with because you can return individual refs that stay reactive when the caller destructures them.
export function useCounter() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
const { count, increment } = useCounter(); // both still reactive
You can't do that cleanly with reactive() — destructuring breaks the connection.
For most of the time I was figuring out the ref/reactive split, I was making it more complicated than it needed to be. Default to ref(). Reach for reactive() when you have a specific reason. That's the whole decision.