iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Options API

Vue’s Options API organises components by KIND of property — data, computed, methods, watch, lifecycle hooks. It’s the original Vue style, still fully supported in Vue 3, and often clearer for small components or teams familiar with the pattern.

data, computed, methods, watch, mixins

EXAMPLE
// 1) Basic component
export default {
    name: 'Counter',
    props: {
        initialValue: { type: Number, default: 0 },
    },
    data() {
        return {
            count: this.initialValue,
            log:   [],
        };
    },
    computed: {
        doubled() { return this.count * 2; },
        message() { return `Count is ${this.count}`; },
    },
    methods: {
        increment() {
            this.count++;
            this.log.push(`incremented to ${this.count}`);
        },
        reset() { this.count = this.initialValue; },
    },
    watch: {
        count(newVal, oldVal) {
            console.log(`changed from ${oldVal} to ${newVal}`);
        },
        // Deep watch on objects
        '$route'(to, from) { console.log('route changed'); },
    },
    mounted() {
        console.log('mounted');
    },
    beforeUnmount() {
        console.log('cleanup');
    },
    template: `
        <div>
            <p>{{ message }} (double: {{ doubled }})</p>
            <button @click="increment">+1</button>
            <button @click="reset">Reset</button>
        </div>
    `,
};

// 2) data must be a function in components — returns fresh state per instance
data() {
    return { count: 0 };          // each instance has its own count
}

// 3) Lifecycle hooks
export default {
    beforeCreate()  {},
    created()       {},
    beforeMount()   {},
    mounted()       {},            // DOM ready; refs available
    beforeUpdate()  {},
    updated()       {},
    beforeUnmount() {},            // cleanup timers, listeners
    unmounted()     {},
    errorCaptured(err, vm, info) { return false; },
};

// 4) Props with validation
props: {
    title:   { type: String,  required: true },
    count:   { type: Number,  default: 0, validator: (v) => v >= 0 },
    tags:    { type: Array,   default: () => [] },
    config:  { type: Object,  default: () => ({}) },
    onClick: { type: Function },
};

// 5) emits — declare events
export default {
    props: ['value'],
    emits: ['update:value', 'submit'],
    methods: {
        onInput(e) { this.$emit('update:value', e.target.value); },
        save()     { this.$emit('submit'); },
    },
};

// 6) Computed with getter + setter (less common)
computed: {
    fullName: {
        get() { return `${this.first} ${this.last}`; },
        set(value) {
            const [first, last] = value.split(' ');
            this.first = first;
            this.last  = last;
        },
    },
};

// 7) Watcher options
watch: {
    user: {
        handler(newVal) { console.log('user changed', newVal); },
        deep: true,                        // watch deeply nested properties
        immediate: true,                   // fire on creation
        flush: 'post',                     // run after DOM update
    },
};

// 8) refs to DOM elements / child components
template: '<input ref="input" />',
mounted() {
    this.$refs.input.focus();
}

// Child component ref
template: '<Child ref="child" />',
methods: {
    callChild() { this.$refs.child.someMethod(); }
}

// 9) Mixins — share logic across components
const loggingMixin = {
    methods: {
        log(msg) { console.log(`[${this.$options.name}] ${msg}`); },
    },
    mounted() { this.log('mounted'); },
};

export default {
    name:    'MyComponent',
    mixins:  [loggingMixin],
    methods: { greet() { this.log('hello'); } },
};

// CAUTION: mixins can conflict + obscure data flow. Prefer composables (Composition API) for new code.

// 10) Inject + provide for dependency injection
// Parent:
export default {
    provide() {
        return { theme: { mode: 'dark', toggle: () => {} } };
    },
};
// Descendant:
export default {
    inject: ['theme'],
    mounted() { console.log(this.theme.mode); },
};

// 11) this.$… runtime properties
// this.$el        — root DOM element
// this.$refs      — template refs
// this.$emit       — emit event
// this.$nextTick  — wait for DOM update
// this.$watch     — programmatic watcher
// this.$forceUpdate — manual rerender (avoid)
// this.$slots / $scopedSlots — passed slot content

// 12) Options API vs Composition API — both supported in Vue 3
// Options API:
//   • Clear structure for small components
//   • Familiar to Vue 2 developers
//   • Mixins for sharing — leak-prone
// Composition API:
//   • Better for large components
//   • Composables (useX) replace mixins cleanly
//   • Better TS inference
//
// Modern guidance for new code: Composition API + <script setup>. But Options API is FINE; not deprecated.

// 13) TypeScript with Options API
import { defineComponent, PropType } from 'vue';
export default defineComponent({
    props: {
        user: { type: Object as PropType<User>, required: true },
    },
    data() {
        return { editing: false as boolean };
    },
    computed: {
        greeting(): string { return `Hi ${this.user.name}`; },
    },
});

// 14) Migrating to Composition API gradually
// Vue 3 supports both in the same component:
import { ref, computed } from 'vue';
export default {
    setup() {
        const count = ref(0);
        const doubled = computed(() => count.value * 2);
        return { count, doubled };
    },
    methods: {
        // can still use 'this' — but mixing is confusing; pick one style per component
        increment() { this.count++; }
    },
};

// 15) Common bugs
// • data() not returning a function in a component → all instances share state
// • Using arrow functions for methods/computed → loses 'this' context
// • watch with immediate but reading 'this' refs not yet mounted → undefined; use 'mounted' or 'flush: post'
// • Mixin name collisions silently overriding — Composition API prevents this
// • Mutating props → Vue warning; use a computed or local copy
// • Forgetting beforeUnmount cleanup → memory leaks
// • Watcher fires for properties that didn't actually change (deep watch on big object) → perf hit
// • Computed accessing $ refs → runs in render context; ensure ref exists

Why it matters

The Options API isn’t deprecated — for small to medium components and teams comfortable with the pattern, it’s perfectly fine in Vue 3. Composition API + <script setup> wins for new code, complex state, and TypeScript inference, but you can mix them. Validate props with types + defaults, declare emits, and reach for composables instead of mixins when sharing logic.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
export default {
    data() { return { count: 0 }; },
    mounted() { console.log('mounted'); },
    methods: { inc() { this.count++; } },
};
Try it Yourself »

Discussion

Loading…