Vue Composition API: Computed and Ref Properties Explained
Source: Dev.to
1️⃣ State – the foundation
State is simply data that can change over time and affects what the user sees on the screen.
If the data changes and the UI must update because of it, that data is state.
Examples of state in a frontend application include:
- a counter value
- a list of users fetched from an API
- whether a modal is open or closed
- the content of an input field
- the currently logged‑in user
If you come from a backend background, you are already familiar with state: database records, request/response objects, session data, cached values… The difference is where it lives.
Traditional backend‑driven flow
- The client sends a request.
- The server processes data.
- The server returns HTML or JSON.
- The page reloads with updated data.
In this model, most of the state lives on the server.
Modern frontend flow (Vue)
- State lives in the browser.
- No full page reload.
- A user clicks a button → data changes in memory → UI updates automatically.
That automatic update is what we call reactivity. Vue needs a way to know which data should trigger UI updates – that’s exactly what ref and computed provide.
2️⃣ ref – reactive state
ref creates reactive values (numbers, strings, booleans, etc.) that Vue watches. When the value changes, Vue automatically updates the UI.
Basic example (Vue 3 + TypeScript)
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
ref(0)tells TypeScript that thisrefcontains a number.- The actual value is accessed via
.value. - When
count.valuechanges, Vue re‑renders any dependent UI.
Using it in a template
{{ count }}
<button @click="increment">Increment</button>
In the template you don’t need
.valuebecause Vue automatically unwraps theref.
ref with objects
When dealing with objects (especially with TypeScript interfaces), you can still use ref.
interface User {
name: string
age: number
}
const user = ref({
name: 'Lou',
age: 27
})
// Updating a property
user.value.age++
If you mainly work with objects, you’ll also encounter the reactive function, but the core idea stays the same: Vue tracks changes and updates the UI when the state changes.
3️⃣ computed – derived (read‑only) state
If ref is your source of truth, computed represents values derived from that source. Derived state is calculated from other state, should always stay in sync, and should not be manually updated.
Simple derived state example
import { ref, computed } from 'vue'
const price = ref(10)
const quantity = ref(2)
const total = computed(() => {
return price.value * quantity.value
})
price&quantityare source state (ref).totalis derived state (computed).
From a backend perspective, this is similar to a calculated column or a DTO field built from other fields. You wouldn’t store total separately in a database because it can always be derived from price * quantity; doing so would create duplication and possible inconsistencies. computed does the same thing on the client side.
computed vs. a plain function
function fullName() {
return `${firstName.value} ${lastName.value}`
}
{{ fullName() }}
Why prefer computed?
| Normal function | computed | |
|---|---|---|
| Runs on each render | ✅ (always) | ❌ (only when deps change) |
| Caching | No | Yes (cached) |
| Dependency tracking | Manual | Automatic |
A normal function runs every time the component re‑renders, even if the underlying values haven’t changed. A computed property is cached; Vue tracks which reactive values it depends on and only recomputes when one of those values changes. In larger components with expensive calculations, computed makes your code more efficient and easier to reason about.
Decision rule
Is this the source of truth, or is it derived from something else?
- Source of truth → use
ref.- Derived → use
computed.
This mental model aligns nicely with a backend mindset focused on data consistency and single sources of truth.
computed with getters and setters
Sometimes you need a computed property that can both read and write data. You can provide a getter and a setter:
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value: string) {
const [first, ...rest] = value.split(' ')
firstName.value = first
lastName.value = rest.join(' ')
}
})
Now fullName can be used like a regular reactive property:
First name: {{ firstName }}
Last name: {{ lastName }}
When the user edits the input, the setter updates firstName and lastName accordingly, and the UI stays in sync.
4️⃣ Recap
| Concept | Purpose | Typical use |
|---|---|---|
ref | Create reactive primitive or object state | Counters, toggles, API responses |
computed | Define derived, cached state based on other refs | Calculated totals, formatted strings, filtered lists |
reactive (not covered in depth) | Make deeply reactive objects (alternative to ref for objects) | Complex nested state |
Remember:
ref= source of truth (mutable).computed= derived, read‑only (unless you add a setter).
Understanding these two building blocks will make you feel right at home with Vue 3’s reactivity system, no matter your backend background.
Happy coding! 🚀
Working with ref and computed in Vue 3 + TypeScript
watch(() => fullName.value, (value) => {
const parts = value.split(' ')
firstName.value = parts[0]
lastName.value = parts[1]
})
Now you can assign a value directly:
fullName.value = 'Lou Creemers'
This is useful in forms where one input field represents multiple pieces of state.
Types in Vue 3 + TypeScript
When using Vue 3 with TypeScript, it’s good to think about types early.
TypeScript can often infer the type automatically:
const isVisible = ref(true) // inferred as Ref<boolean>
You can also define it explicitly (the same syntax works):
const isVisible = ref(true)
For computed, the return type is usually inferred. If needed, you can define it:
const total = computed(() => {
return price.value * quantity.value
})
Being clear about types helps prevent subtle bugs, especially in larger projects or team environments.
Common Mistakes
- Forgetting to use
.valueinside the script. - Trying to change a computed property without defining a setter.
- Using
computedfor something that should just be a normal constant.
If something isn’t updating in your template, the first thing to check is whether you are actually working with a reactive source.
Why ref and computed Matter
ref and computed are central parts of Vue 3’s reactivity model.
Once you understand the difference between source state (ref) and derived state (computed), your components become much easier to reason about.
If you are building applications with Vue 3 and TypeScript, mastering these two concepts will give you a strong foundation for everything that follows.
Got questions?
Feel free to reach out: louella.dev/socials or leave a comment down below.
See ya!