
Vue 3 Composition API Best Practices
Master the Vue 3 Composition API with these proven best practices and patterns.
Tim Le April 16, 2026
Vue 3 Composition API Best Practices
The Composition API in Vue 3 provides a flexible and powerful way to organize component logic. Let's explore some best practices.
Why Use Composition API?
The Composition API offers several benefits:
- Better code organization
- Improved TypeScript support
- Easier code reuse
- Better logic composition
Basic Setup
Use <script setup> for cleaner syntax:
<script setup>
import { ref, computed, onMounted } from "vue"
const count = ref(0)
const doubled = computed(() => count.value * 2)
onMounted(() => {
console.log("Component mounted!")
})
</script>
Composables Pattern
Create reusable composables:
// composables/useCounter.ts
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => (count.value = initialValue)
return {
count: readonly(count),
increment,
decrement,
reset,
}
}
Use it in components:
<script setup>
const { count, increment, decrement } = useCounter(10)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
State Management
For complex state, use composables:
// composables/useAuth.ts
const user = ref(null)
const isAuthenticated = computed(() => !!user.value)
export function useAuth() {
const login = async (credentials) => {
const response = await $fetch("/api/login", {
method: "POST",
body: credentials,
})
user.value = response.user
}
const logout = () => {
user.value = null
}
return {
user: readonly(user),
isAuthenticated,
login,
logout,
}
}
Watchers and Effects
Use watchers wisely:
// Watch a single ref
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
})
// Watch multiple sources
watch([count, doubled], ([newCount, newDoubled]) => {
console.log(`Count: ${newCount}, Doubled: ${newDoubled}`)
})
// Immediate watcher
watchEffect(() => {
console.log(`Current count: ${count.value}`)
})
Props and Emits
Define props and emits with TypeScript:
<script setup lang="ts">
interface Props {
title: string
count?: number
}
interface Emits {
(e: "update", value: number): void
(e: "delete"): void
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
})
const emit = defineEmits<Emits>()
</script>
Lifecycle Hooks
Use lifecycle hooks appropriately:
onMounted(() => {
// Component is mounted
fetchData()
})
onUnmounted(() => {
// Cleanup
clearInterval(timer)
})
onUpdated(() => {
// Component updated
})
Template Refs
Access DOM elements:
<script setup>
const inputRef = ref(null)
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<input ref="inputRef" />
</template>
Best Practices Summary
- Use
<script setup>for cleaner code - Create composables for reusable logic
- Keep composables focused on single responsibility
- Use TypeScript for better type safety
- Leverage computed properties for derived state
- Clean up side effects in
onUnmounted - Use
readonlyto protect reactive state
Conclusion
The Composition API is a powerful tool for building maintainable Vue applications. By following these best practices, you'll write cleaner, more reusable code.
Happy coding!