Reactivity with ref and reactive
The two core primitives for reactive state, when to reach for each, and the .value gotcha that trips up beginners.
読了時間 2 分
Reactivity is the mechanism that lets Vue automatically update the DOM when your data changes, without you writing any manual "re-render this" code. The Composition API exposes two functions for creating reactive state: ref() and reactive().
ref: reactive values of any type
<script setup>
import { ref } from "vue";
const count = ref(0);
const name = ref("Ada");
function increment() {
count.value++;
}
</script>
<template>
<p>{{ name }} has clicked {{ count }} times.</p>
<button @click="increment">+1</button>
</template>ref() wraps a value — a number, a string, an object, anything — in a reactive container. In your <script setup> JavaScript, you access or change that value through .value. Inside the <template>, Vue automatically "unwraps" refs, so you write {{ count }}, not {{ count.value }}.
That .value requirement is the most common beginner trip-up: forgetting it (count++ instead of count.value++) silently reassigns a plain number instead of updating the reactive container, and the DOM won't update. It exists because JavaScript has no way to intercept reads and writes to a plain variable — wrapping the value in an object with a .value property is what lets Vue detect the change.
reactive: reactive objects, no .value
<script setup>
import { reactive } from "vue";
const user = reactive({
name: "Ada",
loginCount: 0,
});
function login() {
user.loginCount++;
}
</script>
<template>
<p>{{ user.name }} has logged in {{ user.loginCount }} times.</p>
<button @click="login">Log in</button>
</template>reactive() only works on objects (and arrays), and it makes every property on that object reactive directly — no .value needed, since Vue can intercept property access on an object using JavaScript Proxies. This reads a little more naturally for grouped state.
Why ref is the more common default
reactive()'s convenience comes with a sharp edge: if you destructure a property off a reactive object, you lose reactivity, because you've copied out the plain value and severed its connection to the proxy.
const user = reactive({ name: "Ada", loginCount: 0 });
const { loginCount } = user; // plain number now — no longer reactiveA ref doesn't have this problem, since you always interact with it through its stable .value property, which you can pass around, return from functions, and destructure freely without losing the connection. Because of this, many Vue codebases default to ref() for everything, including objects, and use reactive() more sparingly.
const user = ref({ name: "Ada", loginCount: 0 });
user.value.loginCount++; // still works, still reactiveBoth are correct tools — reactive() for tightly grouped local state you'll always access through the parent object, ref() for everything else, especially values you need to pass into other functions or composables.