Vue Reactivity: ref, reactive and computed
Reactive data is data that Vue watches, so the page updates whenever it changes.
Plain Variables Are Not Reactive
In the earlier lessons we used plain const values. If you change a plain variable, Vue does not know, and the page stays the same. To make data reactive, use ref() or reactive().
ref()
ref() wraps a value and makes it reactive. It works with any type: numbers, strings, booleans, arrays and objects.
Example
<script setup>
import { ref } from "vue";
const likes = ref(0);
function like() {
likes.value++;
}
</script>
<template>
<p>This post has {{ likes }} likes</p>
<button @click="like">Like</button>
</template>This post has 0 likes (after 2 clicks) This post has 2 likes
The important rule:
| Where | How to read a ref |
|---|---|
In <script setup> | likes.value |
In <template> | likes (Vue adds .value for you) |
Forgetting .value in the script is the most common Vue 3 mistake. If a number does not change, check this first.
reactive()
reactive() makes a whole object reactive. You do not need .value with it.
Example
<script setup>
import { reactive } from "vue";
const order = reactive({
item: "Chicken Biryani",
plates: 1,
pricePerPlate: 450,
});
function addPlate() {
order.plates++;
}
</script>
<template>
<p>{{ order.plates }} x {{ order.item }}</p>
<button @click="addPlate">Add plate</button>
</template>reactive() only works with objects and arrays, and you should not replace the whole object (order = {...}), or it loses reactivity. Because of these limits, many developers simply use ref() for everything.
computed()
A computed value is calculated from other reactive data. Vue remembers the result and recalculates it only when the data it uses changes.
Example
<script setup>
import { ref, computed } from "vue";
const plates = ref(2);
const pricePerPlate = 450;
const total = computed(() => plates.value * pricePerPlate);
const message = computed(() => (total.value > 1000 ? "Free delivery!" : "Delivery Rs 150"));
</script>
<template>
<p>Plates: {{ plates }}</p>
<p>Total: Rs {{ total }}</p>
<p>{{ message }}</p>
<button @click="plates++">Add plate</button>
</template>Plates: 2 Total: Rs 900 Delivery Rs 150 (after 1 click) Plates: 3 Total: Rs 1350 Free delivery!
Use computed values instead of long expressions in the template. Your template stays clean, and the logic is in one place.
Computed vs a Function
You could also write a function function total() { ... } and call {{ total() }}. The difference:
- A computed value is cached and recalculated only when its data changes
- A function runs again on every render
For values derived from data, prefer computed.
watch()
Sometimes you want to do something when data changes, such as saving it or calling an API. Use watch():
Example
<script setup>
import { ref, watch } from "vue";
const city = ref("Karachi");
watch(city, (newCity, oldCity) => {
console.log(`City changed from ${oldCity} to ${newCity}`);
});
</script>
<template>
<button @click="city = 'Lahore'">Move to Lahore</button>
</template>City changed from Karachi to Lahore
Which One to Use?
| Tool | Use it when |
|---|---|
ref() | You need any reactive value (the default choice) |
reactive() | You want a reactive object without .value |
computed() | A value is calculated from other values |
watch() | You need to run code (a side effect) when something changes |
Exercise
Exercise
Inside <script setup>, how do you add 1 to a ref called count?
