Skip to content
All essays
WebMarch 27, 202512 min

Vue 3 Composition API: Complete Guide

Master Vue 3 Composition API. Learn reactivity, composables, lifecycle hooks, and best practices for building scalable Vue applications.

Ü
Ümit Uz
Mobile & Full Stack Developer

The Composition API is a powerful new way to organize component logic in Vue 3, providing better TypeScript support and more flexible code reuse.

Why Composition API?

The Composition API addresses the limitations of the Options API, especially for larger components:

javascript
// Options API - Logic can be scattered
export default {
  data() { return { count: 0 } },
  methods: { increment() { this.count++ } },
  computed: { double() { return this.count * 2 } }
}

// Composition API - Logic is organized by feature
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    const increment = () => count.value++
    return { count, double, increment }
  }
}

Script Setup Syntax

The <script setup> syntax is the recommended way to use the Composition API:

vue
<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)

function increment() {
  count.value++
}

onMounted(() => {
  console.log('Component mounted!')
})
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ double }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

Reactive References

ref()

Used for primitive values and objects:

javascript
import { ref } from 'vue'

const count = ref(0)
const user = ref({ name: 'John' })

// Access value with .value
count.value++
user.value.name = 'Jane'

reactive()

Used for objects (no .value needed):

javascript
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: { name: 'John' }
})

state.count++
state.user.name = 'Jane'

Computed Properties

Create reactive computed values:

javascript
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed(() => `${firstName.value} ${lastName.value}`)

// Writable computed
const fullNameWritable = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value) => {
    [firstName.value, lastName.value] = value.split(' ')
  }
})

Watchers

watch()

Watch specific sources:

javascript
import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})

// Watch multiple sources
watch([count, anotherRef], ([newCount, newAnother]) => {
  console.log('Values changed:', newCount, newAnother)
})

watchEffect()

Automatically track dependencies:

javascript
import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  console.log(`Count is: ${count.value}`)
})

Lifecycle Hooks

Composition API lifecycle hooks:

javascript
import {
  onMounted,
  onBeforeMount,
  onUpdated,
  onBeforeUpdate,
  onUnmounted,
  onBeforeUnmount
} from 'vue'

onMounted(() => {
  console.log('Component mounted')
})

onUpdated(() => {
  console.log('Component updated')
})

onUnmounted(() => {
  console.log('Component unmounted')
})

Composables

Reusable logic functions:

javascript
// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)

  function increment() { count.value++ }
  function decrement() { count.value-- }
  function reset() { count.value = initialValue }

  return {
    count,
    double,
    increment,
    decrement,
    reset
  }
}

// Using the composable
<script setup>
import { useCounter } from './composables/useCounter'

const { count, double, increment } = useCounter(10)
</script>

Props and Emits

vue
<script setup>
const props = defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  }
})

const emit = defineEmits(['update', 'delete'])

function handleClick() {
  emit('update', props.count + 1)
}
</script>

Provide/Inject

javascript
// Parent
import { provide, ref } from 'vue'

const theme = ref('dark')
provide('theme', theme)

// Child
import { inject } from 'vue'

const theme = inject('theme')

Best Practices

  1. 1Use ref() for primitives and reactive() for objects
  2. 2Extract reusable logic into composables
  3. 3Use computed properties for derived state
  4. 4Watchers should be used for side effects
  5. 5Keep setup functions focused and readable

The Composition API provides a more flexible and maintainable way to organize component logic, especially as applications grow in complexity.

Related essays

Next essay
Nuxt 3: The Vue Meta-Framework