Vue.js 3 Composition API Complete Tutorial
Introduction to Composition API
Vue 3's Composition API provides a new way to organize and reuse logic in Vue components. It offers better TypeScript support, improved code organization, and easier logic reuse compared to the Options API.
1. Basic Setup
<script setup>
import { ref, computed, onMounted } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
onMounted(() => {
console.log('Component mounted');
});
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Doubled: {{ doubled }}</p>
<button @click="increment">Increment</button>
</div>
</template>
2. Reactive State
import { ref, reactive, toRefs } from 'vue';
// ref - for primitive values
const count = ref(0);
const message = ref('Hello');
// reactive - for objects
const state = reactive({
user: { name: 'John', age: 30 },
isLoading: false,
errors: []
});
// Destructure with toRefs
const { user, isLoading } = toRefs(state);
// Access values
count.value = 5;
state.user.name = 'Jane';
3. Computed Properties
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
// Read-only computed
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
// Writable computed
const fullNameWritable = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ');
}
});
4. Watchers
import { ref, watch, watchEffect } from 'vue';
const question = ref('');
const answer = ref('');
// Watch single ref
watch(question, async (newQuestion) => {
if (newQuestion.includes('?')) {
answer.value = 'Thinking...';
answer.value = await fetchAnswer(newQuestion);
}
});
// Watch multiple sources
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`Name changed to ${newFirst} ${newLast}`);
});
// watchEffect - runs immediately
watchEffect(() => {
console.log(`Question: ${question.value}`);
console.log(`Answer: ${answer.value}`);
});
// Stop watching
const stop = watch(question, callback);
stop(); // Clean up
5. Lifecycle Hooks
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue';
onBeforeMount(() => {
console.log('Before mount');
});
onMounted(() => {
console.log('Mounted');
// Fetch data, setup listeners
});
onBeforeUpdate(() => {
console.log('Before update');
});
onUpdated(() => {
console.log('Updated');
});
onBeforeUnmount(() => {
console.log('Before unmount');
// Cleanup
});
onUnmounted(() => {
console.log('Unmounted');
});
6. Composables (Custom Hooks)
// composables/useFetch.js
import { ref } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
async function fetch() {
loading.value = true;
try {
const response = await fetch(url);
data.value = await response.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
return { data, error, loading, fetch };
}
// Usage in component
import { useFetch } from './composables/useFetch';
const { data, error, loading, fetch } = useFetch('/api/users');
fetch();
7. Template Refs
<script setup>
import { ref, onMounted } from 'vue';
const inputEl = ref(null);
onMounted(() => {
inputEl.value.focus();
});
</script>
<template>
<input ref="inputEl" type="text" />
</template>
8. Provide / Inject
// Parent component
import { provide, ref } from 'vue';
const theme = ref('dark');
function toggleTheme() {
theme.value = theme.value === 'dark' ? 'light' : 'dark';
}
provide('theme', { theme, toggleTheme });
// Child component (any level)
import { inject } from 'vue';
const { theme, toggleTheme } = inject('theme');
9. Props and Emits with TypeScript
<script setup lang="ts">
interface Props {
title: string;
count?: number;
}
const props = withDefaults(defineProps<Props>(), {
count: 0
});
interface Emits {
(e: 'update', value: number): void;
(e: 'delete'): void;
}
const emit = defineEmits<Emits>();
function handleClick() {
emit('update', props.count + 1);
}
</script>
10. Async Components
import { defineAsyncComponent } from 'vue';
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
);
// With options
const AsyncCompWithOptions = defineAsyncComponent({
loader: () => import('./components/Heavy.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 3000
});
💡 Best Practices:
- Use
<script setup>for cleaner syntax - Prefer
reffor primitive values,reactivefor objects - Extract reusable logic into composables
- Use TypeScript for better type safety
- Clean up side effects in lifecycle hooks
- Use
computedfor derived state - Implement proper error handling
Conclusion
The Composition API is the future of Vue development, offering better code organization, TypeScript support, and reusability. By mastering these patterns, you'll write more maintainable and scalable Vue applications.