Composables Explained
A composable is simply a function that uses Vue's Composition API primitives — ref, reactive, computed, watch, lifecycle hooks — to encapsulate and reuse stateful logic across components. Composables are Vue's answer to the reuse problem that Options API mixins solved poorly: mixins silently merged properties into a component, making it unclear where any given piece of state or method actually came from, and colliding names could overwrite each other unpredictably. Composables avoid this because everything they expose is explicit, returned as named values that the calling component destructures.
Cricket analogy: A composable is like a specialist fielding coach who can be brought into any team's training camp to teach catching drills, versus the old mixin approach which was like silently swapping players' kits before a match without telling anyone; a composable's explicit return values are like the coach handing back a clearly labeled drill sheet.
Anatomy of a composable
By convention, a composable is named starting with use (e.g. useMouse, useFetch, useLocalStorage), lives in its own file (often under a composables/ directory), and returns an object of reactive state and functions rather than a plain non-reactive object. Internally it can use any Composition API feature, including lifecycle hooks, since those correctly attach to whichever component instance calls the composable.
Cricket analogy: Naming a composable useMouse or useFetch is like labeling a training drill 'Fielding Drill 3' so any coach can find it in the team's shared drill folder, and because the drill attaches to whichever squad runs it, the same drill produces separate results for the U19 team and the senior team.
// composables/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 a composable in a component
Consuming a composable looks just like calling any other function inside <script setup>; the returned refs are used directly in the template, and Vue's reactivity system takes care of updating the DOM whenever the composable's internal state changes. Multiple components can call the same composable independently, and each call creates its own isolated instance of the state — there is no shared singleton unless the composable is explicitly written to share module-level state.
Cricket analogy: Calling useMouse() in a component is like a batter calling up their personal stance coach before an innings — each batter who calls the same coach gets individually tailored advice for their own stance, not a shared one, unless the coach is explicitly running a shared team-wide session (module-level state).
<script setup>
import { useMouse } from '@/composables/useMouse'
const { x, y } = useMouse()
</script>
<template>
<p>Mouse position: {{ x }}, {{ y }}</p>
</template>Composables are conceptually very close to React's custom hooks (functions prefixed with use that call other hooks internally). The major structural difference is that Vue composables are ordinary functions with no 'rules of hooks' restricting them to a fixed call order, because Vue's reactivity tracking doesn't depend on call order the way React's hook slots do.
A subtle pitfall: if a composable returns a plain destructured value from a reactive() object instead of refs, the returned value loses reactivity once destructured. Composables should generally return individual refs (or use toRefs() on a reactive object) so consumers can safely destructure the return value without breaking reactivity.
- A composable is a function that encapsulates reusable stateful logic using Composition API primitives.
- Composables replace Options API mixins, avoiding unclear property origins and naming collisions.
- Naming convention: prefix with 'use' and typically place in a composables/ directory.
- Lifecycle hooks called inside a composable correctly attach to whichever component invokes it.
- Each call to a composable creates independent state unless module-level state is deliberately shared.
- Composables should return refs (or toRefs() output) so their state stays reactive after destructuring.
Practice what you learned
1. What is a Vue composable?
2. What problem did composables primarily replace from the Options API?
3. Why do lifecycle hooks work correctly when called inside a composable?
4. What happens if two different components each call the same composable?
5. Why should a composable typically return refs rather than destructured reactive() properties?
Was this page helpful?
You May Also Like
Writing a Custom Composable
A hands-on walkthrough of designing, implementing, and consuming a custom composable, using a realistic data-fetching example.
reactive() and ref()
A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.
Lifecycle Hooks in the Composition API
Learn how onMounted, onUpdated, onUnmounted and other lifecycle hook functions let you run code at specific points in a component's life.
The setup() Function and <script setup>
Learn the entry point of the Composition API — the setup() function — and how <script setup> compiles away its boilerplate.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics