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>

🧠 Test Your Knowledge

Which function creates a reactive reference in Composition API?