Vue 3 Composition API Complete Guide
Why Composition API matters
The Options API is easy to read in small components, but as components grow, related logic gets scattered across data, methods, watch, and computed. The Composition API lets you group that logic by feature.
In other words, its first real benefit is not reuse. It is cohesion.
setup() is the entry point, not the place for everything
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
function increment() {
count.value += 1;
}
return { count, double, increment };
},
};
At first, it is easy to dump all code into setup(), but in real projects it quickly turns into a giant function. That is usually the point where you should start extracting composables.
ref and reactive serve different roles
ref: a single value with a clear state boundaryreactive: an object whose fields move together
In practice, using ref as the default is usually more predictable, and using reactive only when multiple fields semantically belong to one object works better. reactive is convenient, but destructuring and dependency tracking boundaries can become blurry.
A composable is not just a reusable function, but a feature module
import { ref, onMounted } from 'vue';
export function useUserProfile(userId) {
const profile = ref(null);
const loading = ref(false);
const error = ref(null);
async function fetchProfile() {
loading.value = true;
error.value = null;
try {
const res = await fetch(`/api/users/${userId}`);
profile.value = await res.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
onMounted(fetchProfile);
return { profile, loading, error, fetchProfile };
}
A good composable usually encapsulates one feature, such as authentication, a user profile, pagination, or scroll state. That lets UI components focus on presentation while the logic lives in composables.
watch is powerful, but easy to overuse
In the Composition API, watch is useful when you need to react to external changes, but it can also make state flow harder to follow. If something can be expressed with computed, prefer computed first, and use watch only when you really need side effects.
Common problems in real projects
setup()grows so large that it becomes harder to read- one composable takes on networking, state, routing, and UI all at once
- passing
reactiveobjects around too freely and losing tracking boundaries - handling too much logic directly inside templates
The Composition API gives you a lot of freedom, and without structure that freedom can quickly turn into disorder.
Recommended guidelines
- group screen logic into composables
- separate UI presentation state from business state
- keep composable naming consistent with the
useXxxpattern - encapsulate external API calls inside composables, but avoid over-generalizing them
- start small and extract only when repeated logic actually appears
Wrap-up
The point of the Composition API is not to use more features. It is to place logic in a more cohesive structure. setup() is only the starting point, and real implementation quality depends on how well you define composable boundaries. If you get that right, Vue components become much easier to read, more reusable, and far more resilient to change.
What Gets Hard in Production
- Composition API scales well, but only if composables have clear ownership and are not used as a universal dumping ground.
- Cross-cutting reactive state can become hard to trace when composables implicitly share module-level state.
- Reactivity is powerful enough to hide coupling until the app is large and change behavior becomes surprising.
Architecture Decisions That Matter
- Write composables around stable responsibilities such as pagination, auth session access, or form orchestration.
- Make it explicit whether a composable returns isolated state per caller or shared singleton state.
- Keep side effects visible and close to the consumer whenever possible.
Practical Example
A reliable composable exposes one coherent capability and a deliberate state shape:
export function usePagination(initialPage = 1) {
const page = ref(initialPage)
const pageSize = ref(20)
function next() {
page.value += 1
}
return { page, pageSize, next }
}
Anti-Patterns to Avoid
- Moving page-specific logic into a composable just to make files shorter.
- Returning huge reactive bags with unclear invariants.
- Sharing module-level refs without documenting lifecycle and reset behavior.
Operational Checklist
- Review composable naming and ownership periodically.
- Test composables with multiple consumer instances when isolation matters.
- Document any shared-state composables explicitly.
- Watch for reactivity chains that make debugging update triggers difficult.
Final Judgment
The Composition API is strongest when composables create readable behavioral units. It becomes weak when “reusable” simply means “moved elsewhere.”
Continue Reading
Related posts
Vue Router 4 Design Guide
This guide looks at Vue Router 4 not just as a routing tool, but as a way to design page transitions and authorization flow. It covers nested routes, guards, lazy loading, and URL design principles.
🖥️ FrontendVue 3 Component Communication Design Guide
This guide explains when to use props, emit, provide/inject, and stores in Vue 3. It focuses less on syntax and more on how to design data flow and component boundaries.
💬 LanguageTypeScript Utility Types: A Practical Guide
A production-focused guide to TypeScript utility types. Learn how to model DTOs, update payloads, selectors, and derived types without making your type layer harder to read.
💬 LanguageModern JavaScript Syntax Through ES2024
A practical guide to modern JavaScript syntax through an engineering lens. Learn which ES2024-era features genuinely improve code quality and which ones still need restraint.
Next Path