Vue recommends the Composition API over mixins because it avoids implicit merging and name collisions, improves code organization, and offers better TypeScript inference for reusable logic.
Why does Vue recommend the Composition API over mixins today?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
Vue recommends the Composition API over mixins because mixins scale poorly: they hide where logic/state comes from, can collide/override silently, and are hard to type and refactor. Composition API fixes that by making reuse explicit (import + call a composable), composable (combine multiple functions safely), and TypeScript-friendly (inference works through function signatures).
Problem with mixins | What it looks like in code | How Composition API fixes it |
|---|---|---|
Implicit sources ("Where did this come from?") | Template/method uses | You see it: |
Name collisions / overrides | Two mixins (or mixin + component) define the same | Return names you control (rename on destructure) and keep state scoped per composable call |
Hard refactors | Renaming a field in a mixin can break many components in non-obvious ways | Refactor via normal TS/JS tooling: function exports, parameters, return types |
Weak TypeScript inference | Options API + mixins make it hard for TS to know what exists on | Composables are just functions: TS infers return types and narrows correctly |
Hidden coupling and lifecycle side effects | A mixin adds watchers/hooks that run in every component using it, often unexpectedly | Composables make effects explicit and local; you call it only where needed |
Testing friction | Testing mixin behavior often requires mounting components or relying on | Test composables as plain functions (or with minimal Vue reactivity helpers) |
The big pain point: collisions and “action at a distance”
Mixins merge into the component options. If multiple sources define the same key, you can get overrides or merged behavior (hooks). The result: you read a component and can’t confidently tell what runs, in what order, and which definition “wins”.
<!-- Example: mixin collisions are easy to introduce -->
<script>
const SavingMixin = {
data() {
return { loading: false };
},
methods: {
save() {
// ...
}
}
};
const AnalyticsMixin = {
data() {
return { loading: true }; // collision: same key name
},
methods: {
save() {
// collision: same method name (which one runs is non-obvious)
}
}
};
export default {
name: 'UserForm',
mixins: [SavingMixin, AnalyticsMixin],
// component may also define loading/save again
};
</script>
<template>
<button :disabled="loading" @click="save">Save</button>
</template>
Composition API approach: explicit, local, and composable
With composables, logic reuse is just function composition: you import what you need, call it, and use returned refs/functions. Naming is explicit (and renameable), collisions are controlled, and types are predictable.
// useSave.ts
import { ref } from 'vue';
export function useSave() {
const loading = ref(false);
async function save(payload) {
loading.value = true;
try {
// ...api call
} finally {
loading.value = false;
}
}
return { loading, save };
}
// useAnalyticsSave.ts
export function useAnalyticsSave(baseSave) {
async function saveWithAnalytics(payload) {
// track(...)
return baseSave(payload);
}
return { saveWithAnalytics };
}
<!-- UserForm.vue (Composition API, explicit reuse) -->
<script setup>
import { useSave } from './useSave';
import { useAnalyticsSave } from './useAnalyticsSave';
const { loading, save } = useSave();
const { saveWithAnalytics } = useAnalyticsSave(save);
</script>
<template>
<button :disabled="loading" @click="saveWithAnalytics({ /* ... */ })">
Save
</button>
</template>
Interview-ready point | What to say |
|---|---|
Why mixins are discouraged | They create implicit APIs on the component, cause collisions/overrides, and make large codebases harder to reason about and type. |
Why Composition API is preferred | Reusable logic becomes explicit (import + call), composable (combine functions safely), and TypeScript-friendly (good inference). |
How reuse is done today | Use composables (e.g., |
Are mixins forbidden? | No—still supported, but composables are the recommended default for logic reuse in Vue 3-era apps. |
Practical scenario
You want to reuse form validation across multiple Vue components, choosing between a composable or a mixin.
Common pitfalls
- Mixin name collisions that silently override component methods.
- Hidden dependencies in mixins that are hard to trace.
- Overusing composables and making data flow harder to follow.
Composition API is explicit but can be more verbose. Test by reusing the logic in two components and confirming reactivity works the same.