Composable Basics
In the Vue 3 ecosystem, composables represent the cornerstone mechanism for encapsulating and reusing stateful logic through the Composition API. At its core, a composable is simply a regular JavaScript function adhering to the naming convention of starting with use. By leveraging reactive primitives like ref, computed, watch, and lifecycle hooks internally, it packages a self-contained unit of functionality—such as event listeners, timers, or asynchronous requests—that components can invoke on demand.
- Stateful: Unlike pure utility functions, composables typically manage reactive state internally, ensuring that every invocation yields a completely independent instance of that state.
- Reactive: The returned
refandcomputedproperties seamlessly integrate with the component's reactivity system, automatically updating the view whenever the underlying data changes. - Scoped: When called within
setupor<script setup>, any lifecycle hooks registered inside the composable are automatically bound to the currently active component instance. - Convention over Enforcement: The
useXxxnaming style is a strong community best practice rather than a rigid syntax constraint enforced by the language.
// composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
Consuming it within a component:
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, double, increment } = useCounter(10)
</script>
<template>
<button @click="increment">{{ count }} / {{ double }}</button>
</template>
Every component that calls useCounter() receives its own isolated count variable without interfering with others. This fundamental distinction highlights the difference between true logic reuse and global state sharing.
Comparative Overview with Other Patterns
| Pattern | Characteristics | Primary Limitations |
|---|---|---|
| Utility Functions | Stateless, pure functions | Incapable of managing reactive state or lifecycle hooks |
| Mixins (Vue 2) | Reusable state capabilities | Namespace collisions, unclear data sources, weak TypeScript inference |
| Higher-Order Components | Component-level wrapping | Deep component tree nesting, high mental overhead |
| React Hooks | Similar underlying concept | Executes on every render, error-prone dependency arrays |
| Composables | Independent state, clear origin, TS-friendly | Must adhere to strict invocation timing rules |
Unlike React Hooks, a Vue composable executes only once during setup. The returned reactive references remain active indefinitely, completely eliminating the cognitive burden associated with dependency arrays and bypassing the need for caching utilities like useCallback.
The Broad Spectrum of Applications
Composables can encapsulate virtually any combination of state and behavior. Common use cases include:
- Browser API Wrappers: Tracking mouse positions, window dimensions, local storage, or geolocation data.
- Asynchronous Operations: Centrally managing loading, error, and data states for network requests.
- Common Interaction Patterns: Debouncing and throttling, dark mode toggling, or infinite scrolling.
- Domain Logic: Reusable cross-cutting business rules such as shopping carts, permission checks, or form validation.
In modern meta-frameworks like Nuxt, functions placed inside the composables/ directory are automatically imported, further reducing boilerplate. These frameworks also ship with a rich suite of built-in composables—such as useRoute and useFetch—tailored for routing, middleware, view rendering, and data fetching.
Essential Best Practices
- Always prefix function names with
useand keep each file focused on a single responsibility to facilitate tree-shaking and testing. - Prefer returning a plain object containing multiple
refproperties rather than a singlereactiveobject, thereby preserving reactivity when destructuring. - Design inputs to accept reactive sources (such as
refor getter functions) to maximize flexibility and reuse. - Keep Server-Side Rendering (SSR) in mind: avoid accessing browser APIs at the module top level, and ensure side effects like event listeners are properly cleaned up via
onUnmounted.
Mastering the essence of composables—encapsulating reactive logic through functions—serves as the foundation for navigating advanced built-in APIs and custom architectures, marking a pivotal transition from merely writing components to architecting robust logic layers.