Vue Composition API
Modern way to organize Vue components
🎯 What is Composition API?
Composition API is Vue's modern approach to component logic organization. It uses setup() function with reactive refs and composables, offering better code reusability, TypeScript support, and logical grouping compared to Options API.
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
Key Features
setup()
Entry point for Composition API
setup() {
return { count }
}
ref()
Create reactive variables
const count = ref(0)
count.value++
reactive()
Make objects reactive
const state = reactive({
name: 'Vue'
})
Composables
Reusable logic functions
function useCounter() {
return { count }
}
🔹 Basic Setup Example
Simple counter with Composition API:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
}
}
</script>
Output:
Count: 0
🔹 Script Setup Syntax
Shorter syntax with <script setup>:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<script setup> automatically exposes variables and functions to the template. No need to return them!
🔹 ref() vs reactive()
Two ways to create reactive data:
🔸 Using ref()
<script setup>
import { ref } from 'vue'
// For primitives (numbers, strings, booleans)
const count = ref(0)
const name = ref('Vue')
// Access with .value in script
count.value++
// No .value needed in template
</script>
<template>
<p>{{ count }}</p>
<p>{{ name }}</p>
</template>
🔸 Using reactive()
<script setup>
import { reactive } from 'vue'
// For objects
const state = reactive({
count: 0,
name: 'Vue'
})
// No .value needed
state.count++
</script>
<template>
<p>{{ state.count }}</p>
<p>{{ state.name }}</p>
</template>
🔹 Computed Properties
Create derived reactive values:
<template>
<div>
<input v-model="firstName" placeholder="First name">
<input v-model="lastName" placeholder="Last name">
<p>Full name: {{ fullName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>
Output:
Full name: [Combined names]
🔹 Watchers
React to data changes:
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const message = ref('')
// Watch a single ref
watch(count, (newValue, oldValue) => {
message.value = `Count changed from ${oldValue} to ${newValue}`
})
// Watch multiple sources
watch([count, message], ([newCount, newMsg]) => {
console.log('Something changed!')
})
</script>
<template>
<button @click="count++">Count: {{ count }}</button>
<p>{{ message }}</p>
</template>
Output:
Count changed from 0 to 1
🔹 Lifecycle Hooks
Run code at specific component stages:
<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
const message = ref('Hello')
onMounted(() => {
console.log('Component mounted!')
// Fetch data, setup listeners, etc.
})
onUpdated(() => {
console.log('Component updated!')
})
onUnmounted(() => {
console.log('Component unmounted!')
// Cleanup: remove listeners, cancel requests
})
</script>
- onMounted: After component is added to DOM
- onUpdated: After reactive data changes
- onUnmounted: Before component is removed
- onBeforeMount: Before mounting
- onBeforeUpdate: Before updates
🔹 Creating Composables
Reusable logic functions:
// composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}
🔸 Using the Composable:
<script setup>
import { useCounter } from './composables/useCounter'
const { count, increment, decrement, reset } = useCounter(10)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>
🔹 Props and Emits
Component communication with Composition API:
<script setup>
import { defineProps, defineEmits } from 'vue'
// Define props
const props = defineProps({
title: String,
count: {
type: Number,
default: 0
}
})
// Define emits
const emit = defineEmits(['update', 'delete'])
function handleUpdate() {
emit('update', props.count + 1)
}
</script>
<template>
<div>
<h3>{{ title }}</h3>
<p>Count: {{ count }}</p>
<button @click="handleUpdate">Update</button>
</div>
</template>
🔹 Options API vs Composition API
Options API:
- Organized by option types (data, methods, computed)
- Easier for beginners
- Less flexible for code reuse
Composition API:
- Organized by logical concerns
- Better code reusability with composables
- Excellent TypeScript support
- More flexible and powerful
🔹 Complete Example
Todo app with Composition API:
<template>
<div>
<input v-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo">
<button @click="addTodo">Add</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo.id)">×</button>
</li>
</ul>
<p>Total: {{ totalTodos }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const newTodo = ref('')
const todos = ref([])
let nextId = 1
const totalTodos = computed(() => todos.value.length)
function addTodo() {
if (newTodo.value.trim()) {
todos.value.push({
id: nextId++,
text: newTodo.value
})
newTodo.value = ''
}
}
function removeTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>