Solving Logic Fragmentation: Moving from Mixins to Composables in Vue
Stop guessing where your component properties come from. Learn how to replace opaque Vue Mixins with explicit Composables to eliminate namespace collisions and improve type safety.
15 Jul 2026, 15:17 UTC

The Problem with Hidden State
In large Vue applications, you often need to share logic—like window resizing, authentication checks, or API polling—across multiple components. For years, the standard solution was Mixins. However, Mixins introduce a "black box" problem: when a component uses three different mixins, it becomes nearly impossible to tell which mixin provided a specific data property or method without searching through every single file.
This leads to namespace collisions, where two mixins accidentally use the same variable name, silently overwriting each other and creating bugs that are difficult to trace. The solution is the Composable pattern provided by the Composition API.
What are Composables?
A composable is a function that leverages Vue's reactivity system to encapsulate and reuse stateful logic. Unlike Mixins, which "merge" their properties into the component instance, composables are explicit. You call a function, and it returns exactly what the component needs.
This shift moves the architecture from options-based (where logic is split by type: data, methods, computed) to concern-based (where logic is grouped by feature).
Practical Implementation: Tracking Mouse Coordinates
To see the difference, consider a feature that tracks the user's mouse position. Instead of injecting this into a component via a Mixin, we create a standalone function.
The Composable Logic
// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() {
const x = ref(0);
const y = ref(0);
function update(event) {
x.value = event.pageX;
y.value = event.pageY;
}
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
Using it in a Component
<script setup>
import { useMouse } from './useMouse.js';
const { x, y } = useMouse();
</script>
<template>
<p>Mouse position: {{ x }}, {{ y }}</p>
</template>
Comparing the Engineering Trade-offs
| Feature | Mixins (Vue 2 style) | Composables (Vue 3) |
|---|---|---|
| Traceability | Implicit; properties appear "magically" | Explicit; returned via function call |
| Naming | High risk of collisions | Easy to rename via destructuring |
| TypeScript | Difficult to type accurately | Full type inference for return values |
| Organization | Split by Option (data/methods) | Grouped by Logic/Feature |
The Reactivity Trap
One critical limitation to keep in mind is the difference between ref and reactive when returning state. If a composable returns a reactive object, destructuring it in the component will break reactivity.
The Risk: const { x } = reactive({ x: 0 }) creates a plain number, not a reactive reference. To avoid this, always use ref for individual properties or wrap the returned object in toRefs(). This ensures that the component remains synced with the composable's internal state.
Verification and Maintenance
To verify your composable is working correctly, check the Vue DevTools. You should see the state residing within the component's setup context rather than as a merged property of the instance. If you find yourself creating composables for logic that is only used in one single component, you are likely over-abstracting. Keep logic in the component until a second use case emerges to avoid unnecessary fragmentation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.