← All context files

vue3-options-api/basics.md

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

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: