Chapter 86: Vue Instance Options
Vue Instance Options (also called Options API, component options object, or simply the options object)
This is the classic way of defining a Vue component — the way Vue started, the way millions of tutorials still teach it, and the way many medium/large legacy codebases are still written in 2026.
Even if you personally write 100% <script setup> + Composition API today (which is the modern recommendation), you must understand Options API because:
- You will read/maintain old code
- Many job interviews still ask about it
- Some advanced patterns (mixins, plugins, global components) are still written this way
- Vue Devtools still shows data/computed/methods in this format
So let’s go through it like a proper classroom session — step by step, with real examples, and clear explanations of what each option actually does.
1. The Basic Shape of a Vue Component in Options API
Every Vue component in Options API is defined as an object with various named properties:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export default { // All these are optional — you only write what you need name: 'MyComponent', // optional but very useful props: { ... }, data() { return { ... } }, computed: { ... }, methods: { ... }, watch: { ... }, mounted() { ... }, // ... many more } |
This object is what Vue reads to create the component instance.
2. The Most Important Options (in order of daily usage)
A. name (optional but highly recommended)
|
0 1 2 3 4 5 6 |
name: 'UserProfileCard', |
- Used in:
- Vue Devtools (shows component name instead of “Anonymous”)
- Recursive components (<user-profile-card /> inside itself)
- Better error messages & stack traces
2026 tip: Always add name — it costs nothing and saves hours of debugging.
B. props — incoming data from parent
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
props: { user: { type: Object, required: true }, isAdmin: { type: Boolean, default: false }, size: { type: String, default: 'medium', validator(value) { return ['small', 'medium', 'large'].includes(value) } } } |
Modern 2026 way (still Options API style but with better syntax):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
props: { user: { type: Object as PropType<{ id: number; name: string; email: string }>, required: true }, // ... } |
C. data() — local reactive state
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
data() { return { count: 0, isLoading: false, form: { email: '', password: '' } } } |
- Must return a fresh object every time (that’s why it’s a function)
- All properties become reactive → this.count++ triggers re-render
- Accessed via this.count (not this.$data.count — although that also works)
D. computed — derived values (cached & reactive)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
computed: { fullName() { return `${this.user.firstName} ${this.user.lastName}` }, isFormValid() { return this.form.email.includes('@') && this.form.password.length >= 8 }, filteredTodos() { return this.todos.filter(t => !t.done) } } |
- Cached — only re-computes when dependencies change
- Accessed like data: this.fullName
E. methods — functions you call from template/events
|
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 |
methods: { increment() { this.count++ }, async fetchUser() { this.isLoading = true try { const res = await fetch('/api/user') this.user = await res.json() } catch (err) { console.error(err) } finally { this.isLoading = false } }, logout() { this.$emit('logout') } } |
- Can be called from template: @click=”increment”
- Have access to this
F. watch — react to data/computed changes
|
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 |
watch: { count(newVal, oldVal) { console.log(`Count changed from ${oldVal} to ${newVal}`) }, 'user.name'(newName) { console.log('Name changed to:', newName) }, // deep watch user: { handler(newUser) { console.log('User object changed') }, deep: true }, // immediate + deep searchQuery: { handler(newQuery) { this.fetchResults(newQuery) }, immediate: true, deep: true } } |
G. Lifecycle Hooks (most important ones)
|
0 1 2 3 4 5 6 7 8 9 10 |
created() { /* after data & computed are ready, before DOM */ }, mounted() { /* after first render to DOM – perfect for DOM access */ }, updated() { /* after any update */ }, beforeUnmount() { /* cleanup timers, listeners */ }, unmounted() { /* component destroyed */ } |
Modern equivalents in Composition API:
|
0 1 2 3 4 5 6 7 |
onMounted(() => { ... }) onBeforeUnmount(() => { ... }) |
4. Real Example – Full Component in Options API Style
|
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 |
<template> <div class="user-card"> <h2 v-if="user">{{ fullName }}</h2> <p v-else>Loading user...</p> <button @click="incrementAge">+1 year</button> <p>Age: {{ user.age }}</p> <button @click="fetchUser">Refresh User</button> </div> </template> <script> export default { name: 'UserCard', props: { initialUser: { type: Object, default: () => ({ name: 'Guest', age: 0 }) } }, data() { return { user: { ...this.initialUser }, counter: 0 } }, computed: { fullName() { return `${this.user.name} (age ${this.user.age})` } }, methods: { incrementAge() { this.user.age++ }, async fetchUser() { // fake API await new Promise(r => setTimeout(r, 800)) this.user = { name: 'Rahul Sharma', age: 28 } } }, watch: { 'user.age'(newAge) { console.log(`Age changed to ${newAge}`) } }, mounted() { console.log('Component mounted – DOM ready') this.fetchUser() }, beforeUnmount() { console.log('Cleaning up...') } } </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; } </style> |
5. Quick Summary Table – Most Important Options (2026)
| Option | Purpose | Modern Composition API equivalent | Still used in 2026? |
|---|---|---|---|
| name | Component name (Devtools, recursion) | defineOptions({ name: ‘…’ }) | Yes |
| props | Incoming data | defineProps<…>() | Yes (legacy) |
| data() | Local reactive state | ref(), reactive() | Yes (legacy) |
| computed | Derived reactive values | computed(() => …) | Yes (legacy) |
| methods | Functions | normal functions at top level | Yes (legacy) |
| watch | React to changes | watch(source, callback, options) | Yes (legacy) |
| Lifecycle hooks | mounted, beforeUnmount, etc. | onMounted, onBeforeUnmount, etc. | Yes (legacy) |
Final 2026 Advice
- In new projects → use <script setup> + Composition API (no options object)
- When you see options object (data(), methods, computed: { … }, watch: { … }) → it is Options API (legacy style)
- Learn to read Options API — many jobs, many open-source projects, many tutorials still use it
- Never teach beginners Options API as primary — start with <script setup>
- If you need to migrate old code → gradually convert to Composition API (Vue 3 supports both side-by-side)
Your mini homework:
- Create the UserCard component above in Options API
- Change user.age via button → see computed fullName update
- Add a watch on user.age
- Convert the same component to <script setup> → compare readability
Any part confusing? Want to see:
- Full Options API vs Composition API side-by-side project?
- How Options API looks in Vue Devtools?
- Common bugs when mixing Options + Composition?
- Real component written in both styles?
Just tell me — we’ll convert and compare together step by step 🚀
