Vue 3 Options API – Component Basics
A Vue 3 Single File Component (SFC) using the Options API is built around
named option fields (data, methods, computed, watch, etc.) on the
default exported object.
Minimal SFC
<template>
<div>
<p>{{ message }}</p>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
message: 'Hello',
count: 0,
}
},
methods: {
increment() {
this.count++
},
},
}
</script>
<style scoped>
button { padding: 4px 8px; }
</style>
data must be a function
In a component, data returns a fresh object per instance. Sharing one object
across instances is a classic bug.
// ✅ correct
data() {
return { count: 0 }
}
// ❌ shared across every instance
data: { count: 0 }
computed vs methods
computedproperties are cached — only re-run when reactive dependencies change. Use them for derived state.methodsrun every time they're called. Use them for actions.
data() {
return { first: 'Ada', last: 'Lovelace' }
},
computed: {
fullName() { return `${this.first} ${this.last}` }
},
methods: {
greet() { return `Hi ${this.fullName}` }
}
Writable computed
computed: {
fullName: {
get() { return `${this.first} ${this.last}` },
set(val) {
const [first, ...rest] = val.split(' ')
this.first = first
this.last = rest.join(' ')
},
},
}
watch
React to a value change with a side effect.
watch: {
// simple
query(newVal, oldVal) {
this.fetchResults(newVal)
},
// with options
user: {
handler(val) { console.log(val) },
deep: true,
immediate: true,
},
// path syntax for nested
'user.name'(newVal) {
this.dirty = true
},
}
Use watch for things you can't express as computed (async work, debounced
calls, imperative side effects).
Template refs
<template>
<input ref="myInput" />
</template>
<script>
export default {
mounted() {
this.$refs.myInput.focus()
},
}
</script>
KYD house style
KYD uses Options API for new code unless an existing project already uses Composition. Options API is preferred because:
- A single
thismakes call sites read top-down - Lifecycle, data, and behaviour are co-located by name (not interleaved)
- It maps cleanly to mixins and the broader Vue 2 codebase still in service