Chapter 89: Vue ‘computed’ Option
The computed option
This is the original, classic way to define derived / calculated values that automatically stay in sync with other reactive data. It is still extremely common in legacy codebases, in many job interviews (especially in India), in older tutorials, and in some advanced patterns — even in 2026.
Even if you personally write only <script setup> + Composition API today (which is the modern recommendation), you must understand the computed option properly because:
- You will read/maintain old code written this way
- Many job interviews still ask about it
- Vue Devtools still shows computed properties in this format
- Some plugins / mixins still rely on Options API
So let’s go through it step by step — like I’m pair-programming with you and explaining both the old world and why we mostly moved away from it.
1. What is the computed option? (Very simple mental model)
computed is an object where each key is the name of a derived value, and each value is a function that computes that value based on other reactive data (props, data, other computeds, etc.).
Vue automatically:
- caches the result
- re-computes only when one of the dependencies changes
- makes the computed property reactive → any change triggers re-render where it’s used
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
computed: { fullName() { return `${this.firstName} ${this.lastName}` }, isAdult() { return this.age >= 18 }, filteredTodos() { return this.todos.filter(todo => !todo.done) }, totalPrice() { return this.cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0) } } |
After Vue processes this:
- You can use {{ fullName }} or this.fullName in template / methods
- It only re-calculates when firstName or lastName changes — not on every render
- It behaves exactly like a property — no need to call it like a function
2. Real, Complete Example – User Profile Card with Computed Properties
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
<template> <div class="user-card"> <img :src="avatarUrl" alt="Avatar" class="avatar" /> <h2>{{ fullName }}</h2> <p class="email">{{ email }}</p> <div class="status" :class="statusClass"> {{ statusText }} </div> <p>Age: {{ age }} → {{ ageGroup }}</p> <p v-if="canEdit">You have edit permissions</p> <p v-else>You are viewing in read-only mode</p> <button @click="incrementAge">+1 year (for demo)</button> </div> </template> <script> export default { name: 'UserProfileCard', props: { user: { type: Object, required: true }, isAdmin: { type: Boolean, default: false } }, data() { return { localAge: this.user.age // local copy to demo mutation } }, // ── This is the computed option ─────────────────────────────── computed: { // Simple computed fullName() { return `${this.user.firstName} ${this.user.lastName}` }, // Computed from prop + local data age() { return this.localAge }, // Conditional computed ageGroup() { if (this.age < 13) return 'Child' if (this.age < 20) return 'Teen' if (this.age < 40) return 'Young Adult' if (this.age < 60) return 'Adult' return 'Senior' }, // Class binding computed (very common pattern) statusClass() { return { 'status-active': this.user.isActive, 'status-inactive': !this.user.isActive, 'status-admin': this.isAdmin } }, // Text derived from multiple sources statusText() { if (this.isAdmin) return 'Admin – Full Access' return this.user.isActive ? 'Active User' : 'Inactive User' }, // Permission computed canEdit() { return this.isAdmin || this.user.role === 'editor' }, // Avatar with fallback avatarUrl() { return this.user.avatar || 'https://via.placeholder.com/150?text=User' } }, methods: { incrementAge() { this.localAge++ // this.ageGroup will auto-update because it depends on age } } } </script> <style scoped> .user-card { padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 400px; margin: 2rem auto; text-align: center; } .avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; margin-bottom: 1rem; } .status { padding: 0.5rem 1rem; border-radius: 9999px; font-weight: bold; margin: 1rem 0; display: inline-block; } .status-active { background: #dcfce7; color: #166534; } .status-inactive { background: #fee2e2; color: #991b1b; } .status-admin { background: #dbeafe; color: #1e40af; } </style> |
3. Important Rules & Gotchas (2026 Must-Know)
| Rule / Gotcha | Correct Behavior / Best Practice |
|---|---|
| Computed must be functions | fullName() { … } — never fullName: value |
| Never mutate inside computed | Computed should be pure — read-only, no side effects |
| Access via this | this.fullName (not this.fullName()) — Vue calls it for you |
| Caching | Only re-computes when dependencies change — very performant |
| Can depend on props, data, other computeds | Yes — this.user.name, this.count, this.otherComputed all work |
| Can be used in template & JS | {{ fullName }} or this.fullName in methods/watch |
| Arrow functions? | Avoid — fullName: () => {} loses this binding → this becomes undefined |
| Still used in 2026? | Yes — in legacy code, some plugins, interviews, but not in new <script setup> code |
4. Modern Vue 3 – Why We Almost Never Use computed: { … } Anymore
In <script setup> (the 2026 standard):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<script setup> import { ref, computed } from 'vue' const firstName = ref('Rahul') const lastName = ref('Sharma') // Just a normal const — no `computed` object const fullName = computed(() => { return `${firstName.value} ${lastName.value}` }) // Can even destructure const { name, age } = defineProps<{ name: string; age: number }>() const ageGroup = computed(() => { if (age < 18) return 'Minor' return 'Adult' }) </script> |
→ No computed object → No this → Just normal JavaScript constants → Same caching & reactivity, much cleaner & type-safe with TypeScript
Quick Summary Table – computed in 2026
| Question | Options API (legacy) | Composition API (<script setup>) | What you should do in new code |
|---|---|---|---|
| How to define computed? | computed: { fullName() { … } } | const fullName = computed(() => …) | Composition API |
| Access in template | {{ fullName }} | {{ fullName }} | — |
| Access inside function | this.fullName | fullName.value | — |
| Still used in 2026? | Yes — in legacy code, some plugins, interviews | Almost never (except legacy migration) | Avoid unless maintaining old code |
| TypeScript friendly? | Poor | Excellent (computed<ReturnType>(() => …)) | Use Composition |
Final 2026 Advice from Real Projects
- In new projects → you should never write computed: { … } — just use computed(() => …) in <script setup>
- When you see computed inside an object → it means Options API (legacy style)
- Learn to read Options API — many jobs, open-source projects, tutorials still use it
- Never teach beginners computed option as primary — start with computed(() => …) in <script setup>
- If migrating old code → gradually convert computed → computed(() => …), etc.
Your mini homework:
- Create the UserCard component above in Options API
- Change user.age via button → see computed ageGroup & fullName update automatically
- Console.log this.fullName in mounted → see cached value
- Convert it to <script setup> → compare readability & TypeScript support
Any part confusing? Want to see:
- Full Options API vs Composition API side-by-side project?
- How computed looks in Vue Devtools?
- Common bugs when using arrow functions in computed?
- Real component written in both styles?
Just tell me — we’ll convert and compare together step by step 🚀
