Tim Le 👨🏾‍🚀


About Me

I'm Tim, a software engineer with a strong sense for design. I value clean UI, thoughtful UX, and simple tools. This is my digital garden. 🌱


Home
Blogs
Stacks
Projects
Tools
Back to Blogs
Vue 3 Composition API Best Practices

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 #javascript #composition-api

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

  1. Use <script setup> for cleaner code
  2. Create composables for reusable logic
  3. Keep composables focused on single responsibility
  4. Use TypeScript for better type safety
  5. Leverage computed properties for derived state
  6. Clean up side effects in onUnmounted
  7. Use readonly to 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!

Back to All Posts
POWERED BY TIM LE, 2026
Contact Me