Table of contents [Show]
- Introduction
- Step 1: Setting Up Vue 3 with Composition API
- Step 2: Understanding Reactive Primitives
- Step 3: Lifecycle Hooks in Composition API
- Step 4: Creating Reusable Composables
- Step 5: Advanced Reactivity Patterns
- Step 6: Real-World Example — Todo App with Composition API
- Step 7: Best Practices with Composition API
- Conclusion
Introduction
If you’ve worked with Vue for a while, you know the Options API: data, methods, computed, lifecycle hooks. It’s simple, beginner-friendly, but once your app grows, things get messy. Related logic spreads across multiple options, and reusing functionality is often painful.
Enter the Composition API. Instead of scattering code across options, you group logic by functionality using reactive primitives like ref, reactive, and computed. The result? Clean, modular, and highly reusable Vue code.
Step 1: Setting Up Vue 3 with Composition API
Good news: the Composition API is built into Vue 3. If you scaffold a project with npm init vue@latest or vue-cli, you’re good to go. You’ll use <script setup> for concise syntax.
Example Starter
<script setup>
import { ref } from "vue";
const message = ref("Hello, Composition API!");
</script>
<template>
<h2>{{ message }}</h2>
</template>
Here, ref creates a reactive reference. Unlike plain variables, changing message.value automatically updates the UI.
Step 2: Understanding Reactive Primitives
ref()
ref() wraps primitive values (strings, numbers, booleans) into reactive objects.
import { ref } from "vue";
const counter = ref(0);
function increment() {
counter.value++;
}
reactive()
reactive() is best for objects and arrays. It returns a proxy that tracks nested properties.
import { reactive } from "vue";
const user = reactive({
name: "Alice",
age: 25
});
user.age++; // reactive update
computed()
computed() creates derived values that automatically recalculate when dependencies change.
import { computed, ref } from "vue";
const firstName = ref("John");
const lastName = ref("Doe");
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
Step 3: Lifecycle Hooks in Composition API
Instead of created() or mounted(), you use imported hooks.
import { onMounted, onUnmounted } from "vue";
onMounted(() => {
console.log("Component mounted!");
});
onUnmounted(() => {
console.log("Component destroyed!");
});
Hooks can be used anywhere inside <script setup>, making them easier to organize by logic rather than component structure.
Step 4: Creating Reusable Composables
One of the biggest wins of the Composition API is composables—functions that encapsulate reactive logic for reuse.
Example: useCounter.js
// composables/useCounter.js
import { ref } from "vue";
export function useCounter(initial = 0) {
const count = ref(initial);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, increment, decrement };
}
Using the Composable
<script setup>
import { useCounter } from "./composables/useCounter";
const { count, increment, decrement } = useCounter(10);
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
Now you can reuse this useCounter composable across multiple components.
Step 5: Advanced Reactivity Patterns
watch()
Use watch to react to changes in reactive values.
import { ref, watch } from "vue";
const search = ref("");
watch(search, (newVal, oldVal) => {
console.log("Search changed:", oldVal, "→", newVal);
});
watchEffect()
watchEffect runs immediately and tracks dependencies automatically.
import { ref, watchEffect } from "vue";
const price = ref(100);
const quantity = ref(2);
watchEffect(() => {
console.log("Total:", price.value * quantity.value);
});
shallowRef and shallowReactive
Sometimes you don’t want deep reactivity—for performance reasons. That’s where shallowRef and shallowReactive come in.
Step 6: Real-World Example — Todo App with Composition API
Let’s build a small but real-world Todo app using the Composition API. This example covers reactivity, computed properties, composables, and persistence with Local Storage.
composables/useTodos.js
import { ref, watch } from "vue";
export function useTodos() {
const todos = ref(JSON.parse(localStorage.getItem("todos") || "[]"));
const newTodo = ref("");
function addTodo() {
if (newTodo.value.trim() === "") return;
todos.value.push({ text: newTodo.value, completed: false });
newTodo.value = "";
}
function toggleTodo(index) {
todos.value[index].completed = !todos.value[index].completed;
}
function removeTodo(index) {
todos.value.splice(index, 1);
}
watch(todos, (val) => {
localStorage.setItem("todos", JSON.stringify(val));
}, { deep: true });
return { todos, newTodo, addTodo, toggleTodo, removeTodo };
}
TodoApp.vue
<script setup>
import { useTodos } from "./composables/useTodos";
const { todos, newTodo, addTodo, toggleTodo, removeTodo } = useTodos();
</script>
<template>
<div>
<h1>My Todos</h1>
<input v-model="newTodo" placeholder="Add todo" @keyup.enter="addTodo" />
<button @click="addTodo">Add</button>
<ul>
<li v-for="(todo, index) in todos" :key="index">
<input type="checkbox" v-model="todo.completed" @change="toggleTodo(index)" />
<span :>
{{ todo.text }}
</span>
<button @click="removeTodo(index)">Delete</button>
</li>
</ul>
</div>
</template>
This Todo app is small but powerful—it persists todos in localStorage, demonstrates reactivity, and organizes logic in a composable for reuse.
Step 7: Best Practices with Composition API
- Group by feature, not type: Keep related logic (state, functions, watchers) together.
- Use composables liberally: They’re the key to code reuse and separation of concerns.
- Prefer ref for primitives, reactive for objects.
- Be mindful of performance: Shallow reactivity and
watchEffectare advanced tools for optimization.
Conclusion
The Vue 3 Composition API is a game changer for advanced developers. It brings flexibility, better code organization, and powerful reusability. With reactive primitives, composables, and advanced patterns, you can build apps that are easier to maintain and scale.
Whether you’re building a Todo app or a large-scale enterprise dashboard, the Composition API will be your best companion. Now, take this knowledge and refactor one of your existing projects—you’ll feel the difference immediately.






