Options API
Options API
Level 1 — Core Concepts & Reactivity The legacy object-based component authoring model in Vue, where component logic is organized into predefined options properties like
data,methods, andcomputed.
1. Prerequisites
- Components — The fundamental UI building blocks defined via options objects.
- Declarative Rendering — The rendering model managed by Options API components.
2. Term Category
Vue Component Paradigm / Legacy Authoring Syntax (Options Object Model): The Options API is Vue's classic component structure introduced in Vue 1 and 2, and fully supported in Vue 3 for backward compatibility. Component options are declared as a single options object with dedicated properties (data(), methods, computed, watch, lifecycle hooks).
During instance initialization, Vue processes this options object, converting data() returns into reactive proxies and binding option methods to the internal component instance (this). While easy to learn for beginners and applicable in client/server components, it suffers from rigid code splitting and poor TypeScript inference compared to the Composition API.
3. Explanation
(1) Design Motivation — "Why did we design this?"
In early frontend development, structured component standards were non-existent. Developers frequently created haphazard object literal patterns or messy class abstractions. The Options API was designed to establish an intuitive, beginner-friendly convention: every Vue component followed an identical, predictable structure.
State belonged in data(), actions in methods, cached calculations in computed, side effects in watch, and lifecycle handlers in dedicated lifecycle hooks (mounted, created). This declarative blueprint made Vue exceptionally approachable and easy to learn.
However, as applications scaled to enterprise complexity, the Options API revealed structural limitations:
- Logical Fragmentation: In a 1,000-line component managing three features (e.g., search, pagination, cart), code for a single feature was physically fragmented across
data,methods, andcomputedbuckets. - Reuse Flaws: Reusing stateful logic relied on Mixins, which introduced implicit dependencies, namespace collisions, and opaque source tracking.
- TypeScript Barriers: Inferring types across
thiscontexts required complex TypeScript wrapper abstractions.
These drawbacks led directly to the creation of the modern Composition API.
(2) Reality Metaphor
Imagine filing paperwork in a pre-labeled accordian folder (Options API) versus organizing documents in modular project binders (Composition API).
In the accordian folder, all receipts must go in Slot A (Data), all contracts in Slot B (Methods), and all summaries in Slot C (Computed). If you are working on "Project Alpha", its receipt is in Slot A, its contract in Slot B, and its summary in Slot C. To review Project Alpha, you must flip between three different slots every time.
The Composition API gives you dedicated project binders. You place Project Alpha's receipts, contract, and summary together in Binder 1, and Project Beta's documents in Binder 2.
(3) Vue Code Examples
Short Snippet
<script>
export default {
data() {
return {
count: 0
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
<template>
<button @click="increment">Count: {{ count }} (Double: {{ doubleCount }})</button>
</template>
Fuller Example
<script>
export default {
name: 'UserProfileEditor',
data() {
return {
user: {
firstName: 'Sarah',
lastName: 'Connor',
role: 'Administrator'
},
isEditing: false,
saving: false
}
},
computed: {
fullName() {
return `${this.user.firstName} ${this.user.lastName}`
},
isAdmin() {
return this.user.role === 'Administrator'
}
},
methods: {
toggleEdit() {
this.isEditing = !this.isEditing
},
async saveProfile() {
this.saving = true
try {
// Simulated async API save operation
await new Promise(resolve => setTimeout(resolve, 500))
this.isEditing = false
} finally {
this.saving = false
}
}
},
mounted() {
console.log(`User Profile loaded for: ${this.fullName}`)
}
}
</script>
<template>
<div class="profile-card">
<h2>{{ fullName }} <span v-if="isAdmin">(Admin)</span></h2>
<div v-if="isEditing">
<input v-model="user.firstName" placeholder="First Name" />
<input v-model="user.lastName" placeholder="Last Name" />
<button :disabled="saving" @click="saveProfile">
{{ saving ? 'Saving...' : 'Save' }}
</button>
</div>
<button v-else @click="toggleEdit">Edit Profile</button>
</div>
</template>
4. Common Mistakes & Pitfalls
Mistake 1: Omission of this When Accessing Component Properties
The mistake: Accessing data or methods inside option functions without the this prefix (e.g. writing count++ inside a method).
Why it's wrong: In the Options API, data, methods, and computed properties are bound to the component instance via this. Omitting this searches for a local JavaScript variable in scope and throws a ReferenceError.
Incorrect:
methods: {
increment() {
count++ // ❌ ReferenceError: count is not defined!
}
}
Fix:
methods: {
increment() {
this.count++ // Correctly accesses instance property via 'this'
}
}
Mistake 2: Defining Methods or Computed Properties with ES6 Arrow Functions
The mistake: Declaring option methods using arrow syntax (e.g. methods: { increment: () => { this.count++ } }).
Why it's wrong: Arrow functions bind this lexically to the parent scope (window/undefined) rather than the Vue component instance. Accessing this.count inside an arrow option method will throw a TypeError.
Incorrect:
methods: {
increment: () => {
this.count++ // ❌ 'this' is lexically bound to window/undefined!
}
}
Fix:
methods: {
increment() {
this.count++ // Standard function syntax binds 'this' to component instance
}
}
Mistake 3: Returning Plain Objects from data Option in Component Definitions
The mistake: Declaring data: { count: 0 } as a plain object literal instead of a factory function.
Why it's wrong: When data is a plain object, all instantiated component instances share the exact same memory reference. Mutating state in one instance mutates state across all instances globally.
Incorrect:
export default {
data: { count: 0 } // ❌ Shared object reference across component instances!
}
Fix:
export default {
data() {
return { count: 0 } // Returns a fresh data object per instance
}
}
5. Practice Exercises
Exercise 1: E-Commerce Discount Checkout Calculator
Scenario: A legacy Vue 2 component needs to calculate order cart discounts using the Options API structure. Requirements:
- Declare
data()returningcartTotalandpromoCode. - Implement
computeddiscountedTotalapplying 10% for code'SAVE10'. - Implement
methodapplyCode(code). - Validate calculation via assertions.
Answer
Implementation
export default {
name: 'CartCheckout',
data() {
return {
cartTotal: 150,
promoCode: ''
}
},
computed: {
discountedTotal() {
if (this.promoCode === 'SAVE10') {
return this.cartTotal * 0.9
}
return this.cartTotal
}
},
methods: {
applyCode(code) {
this.promoCode = code
}
},
created() {
// Verification assertions
console.assert(this.discountedTotal === 150, 'Initial total should be 150')
this.applyCode('SAVE10')
console.assert(this.discountedTotal === 135, 'Total should be 135 with SAVE10')
}
}
Technical Explanation
- Data factory function:
data()returns a fresh state object per instance invocation. - Instance
thisbinding:this.cartTotalandthis.promoCodedereference options through instance proxies. - Computed caching:
discountedTotalrecalculates only whenpromoCodeorcartTotalmutates. - Lifecycle invocation:
created()hook runs during instance setup before DOM mounting.
Exercise 2: Industrial Networking Device Gateway Monitor
Scenario: A gateway monitoring component tracks connected IoT devices and displays status logs using Options API. Requirements:
- Declare
data()withdevicesarray andactiveFilterstring. - Compute
filteredDevicesbased on status filter. - Provide method
disconnectDevice(id). - Assert filtering behavior.
Answer
Implementation
export default {
name: 'DeviceGateway',
data() {
return {
devices: [
{ id: 'DEV-01', status: 'online' },
{ id: 'DEV-02', status: 'offline' }
],
activeFilter: 'all'
}
},
computed: {
filteredDevices() {
if (this.activeFilter === 'all') return this.devices
return this.devices.filter(d => d.status === this.activeFilter)
}
},
methods: {
disconnectDevice(id) {
const dev = this.devices.find(d => d.id === id)
if (dev) dev.status = 'offline'
}
},
created() {
console.assert(this.filteredDevices.length === 2, 'Should display 2 devices initially')
this.activeFilter = 'offline'
console.assert(this.filteredDevices.length === 1, 'Should display 1 offline device')
}
}
Technical Explanation
- Option compartmentalization: State, computed state, and methods reside in separated structural sections.
thisaccess in methods: Methods mutate array contents by referencingthis.devices.- Computed filtering:
filteredDevicesreacts automatically to mutations onactiveFilterordevices. - No script setup: Options API uses
export default { ... }configuration objects.
Exercise 3: Financial Analytics Portfolio Metric Tracker
Scenario: A portfolio manager calculates asset balances and gains using Options API options. Requirements:
- Track
cashandstockValueindata(). - Compute
totalNetWorthincomputed. - Provide
depositCash(amount)method. - Verify net worth calculation via test assertion.
Answer
Implementation
export default {
name: 'PortfolioTracker',
data() {
return {
cash: 5000,
stockValue: 12000
}
},
computed: {
totalNetWorth() {
return this.cash + this.stockValue
}
},
methods: {
depositCash(amount) {
this.cash += amount
}
},
created() {
console.assert(this.totalNetWorth === 17000, 'Net worth should equal 17000')
this.depositCash(3000)
console.assert(this.totalNetWorth === 20000, 'Net worth should equal 20000 after deposit')
}
}
Technical Explanation
- Standard method binding:
depositCashuses standard function syntax sothispoints to component. - Derivation rules:
totalNetWorthderives data without side effects. - Legacy compatibility: Options API works seamlessly in Vue 2 and Vue 3 projects alike.
- Refactoring paths: Options API components can be systematically refactored into Composition API
<script setup>syntax.
6. Related Terms
- Composition API — The modern replacement for the Options API in Vue 3.
- Vue Instance — The component instance context accessed via
this. - Reactive State — The underlying reactivity created by the
data()option. - Computed Properties — The derived evaluation option inside Options API components.
7. Key Takeaways
- The Options API organizes component code into fixed option properties (
data,methods,computed,watch). - Accessing state or methods inside Options API components requires prefixing properties with
this.. - Always use standard ES6 method syntax rather than arrow functions to maintain correct
thisbinding. dataMUST be a factory function returning a fresh object to prevent shared state leaks across instances.- While fully supported in Vue 3, new greenfield projects should prefer the Composition API.