Vue 3 Composition API Patterns
Prefer Composition API with script setup for better TypeScript support and reusable logic patterns.
Overview
The Vue 3 Composition API represents a fundamental shift in how Vue applications are structured, offering superior TypeScript integration and enabling patterns for logic reuse that weren't possible with the Options API. The script setup syntax is the recommended way to use Composition API as it provides both compile-time optimizations and reduced boilerplate. The core advantage of Composition API is the ability to colocate related logic regardless of what options (data, computed, methods) it uses. Previously, Vue components required scattering related logic across multiple options, making complex features difficult to maintain. With composables, you can extract and reuse stateful logic across components while maintaining reactivity. The reactive() and ref() primitives form the foundation of Vue's reactivity system. ref() is designed for primitive values and returns a reactive object with a .value property, while reactive() creates a reactive proxy for objects. Understanding when to use each is important for performance—reactive() is slightly more efficient for objects but cannot be destructured without losing reactivity, while refs work for any type but require .value access. Lifecycle hooks in Composition API use the on prefix followed by the hook name, making them easy to identify and enabling tree-shaking for unused hooks. The onMounted, onUpdated, and onUnmounted hooks replace the corresponding options API lifecycle methods. New hooks like onRenderTracked and onRenderTriggered provide debugging capabilities for reactivity dependencies. Computed properties use the computed() function and automatically track their dependencies, recalculating only when necessary.
Code Example
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { useAuth } from '@/composables/useAuth';
import { useTodos } from '@/composables/useTodos';
interface Props {
userId: string;
initialFilter?: 'all' | 'active' | 'completed';
}
const props = withDefaults(defineProps<Props>(), {
initialFilter: 'all',
});
const emit = defineEmits<{
(e: 'select', id: string): void;
(e: 'update:title', value: string): void;
}>();
const { user, isAuthenticated } = useAuth();
const {
todos,
filteredTodos,
addTodo,
toggleTodo,
deleteTodo,
pendingCount,
} = useTodos(props.userId);
const newTodoTitle = ref('');
const filter = ref(props.initialFilter);
const displayTodos = computed(() => {
return filter.value === 'all'
? todos.value
: filteredTodos.value(filter.value);
});
async function handleAddTodo() {
if (!newTodoTitle.value.trim()) return;
await addTodo({
title: newTodoTitle.value,
userId: props.userId,
completed: false,
});
newTodoTitle.value = '';
}
onMounted(() => {
console.log('TodoList mounted for user:', props.userId);
});
watch(filter, (newFilter) => {
emit('update:title', 'Todos - ' + newFilter);
});
</script>
<template>
<div class="todo-list">
<header class="header">
<h1>Welcome, {{ user?.name || 'Developer' }}</h1>
<span class="pending-count">{{ pendingCount }} tasks remaining</span>
</header>
<form @submit.prevent="handleAddTodo" class="add-form">
<input
v-model="newTodoTitle"
type="text"
placeholder="What needs to be done?"
aria-label="New todo input"
/>
<button type="submit" :disabled="!newTodoTitle.trim()">
Add Task
</button>
</form>
<div class="filters">
<button
v-for="f in ['all', 'active', 'completed'] as const"
:key="f"
:class="{ active: filter === f }"
@click="filter = f"
>
{{ f }}
</button>
</div>
<ul class="todos">
<li
v-for="todo in displayTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
<span @click="emit('select', todo.id)">{{ todo.title }}</span>
<button @click="deleteTodo(todo.id)" aria-label="Delete todo">
×
</button>
</li>
</ul>
</div>
</template>More Vue.js Rules
Vue 3 Pinia State Management
Implement centralized state management with Pinia for Vue 3 applications.
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { User, Project } from '@/types';
import { userApi } from '@/api...Vue 3 Routing with Vue Router 4
Implement navigation guards, lazy loading, and advanced routing patterns in Vue 3.
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router';
import { computed, onMounted } from 'vue';
import { useAuthStore } from '@/...Vue 3 Composables Pattern
Extract and reuse stateful logic with Vue 3 Composables using the Composition API.
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import { useLocalStorage } from './useLocalStorage';
import { useFetch } from './u...