Vue.js Composition API

Published Mar 20, 2023

The content here is under the Attribution 4.0 International (CC BY 4.0) license

A comprehensive guide to Vue 3’s Composition API, its core concepts, patterns, and best practices. This guide draws from multiple sources including the VueSchool.io course by Daniel Kelly and official Vue documentation.

Why Use the Composition API?

The Composition API addresses fundamental limitations of traditional Vue options:

  • Solves mixin issues: Avoids naming collisions and implicit dependencies
  • Better TypeScript support: Enables full type inference and safety

Core Concepts: Setup & Composition

The Setup Function

The setup() function serves as the entry point for the Composition API. It can be declared in two ways:

  • <script setup> (Vue 3.2+) - No explicit return needed
  • export default { setup() {} } - Must return reactive data

Key responsibilities:

  • Receives props as the first argument
  • Receives context as the second argument
  • Returns data and functions exposed to the template

Reactivity Fundamentals

Understanding Ref vs. Reactive

Vue provides two ways to create reactive state, each with distinct purposes:

ref() - Universal reactivity wrapper

  • Works with both primitive and non-primitive values
  • Requires .value to access in JavaScript
  • Accessed directly in templates (Vue handles unwrapping)
  • Supports variable reassignment
  • Allows object destructuring without breaking reactivity

reactive() - Object-only reactivity

  • No .value required
  • Direct property access and destructuring with toRef
  • Cannot be reassigned (breaks reactivity)
  • Only works with objects and arrays

Best Practices

  • Copy props to local state: Avoid reference issues and maintain reactivity isolation
  • Use const: Prevents accidental reactivity loss through reassignment
  • Remember the distinction: Objects stored by reference, primitives need ref()

Advanced Reactivity Utilities

  • isReadonly() - Check if value is read-only
  • isRaw() - Check if value is a raw object
  • markRaw() - Mark object to skip reactivity
  • shallowReactive() - Only root-level reactivity
  • shallowReadonly() - Only root-level read-only protection

Computed Properties

The computed() function creates derived reactive values with optional getter/setter:

const fullName = computed({
  get: () => `${first.value} ${last.value}`,
  set: (newValue) => {
    // update first and last
  }
})

Watchers: Tracking State Changes

watch() - Targeted Observation

Monitor specific reactive values and respond to changes:

  • First argument: The variable to watch
  • Second argument: Callback function with (newValue, oldValue)
  • Options:
    • immediate: true - Fire callback on initial setup
    • deep: true - Watch nested properties
  • Cleanup: Call the return value to unsubscribe

Note: When watching arrays created with reactive() or ref(), create a copy to trigger properly.

watchEffect() - Automatic Dependency Tracking

Automatically registers all dependencies accessed inside the callback:

  • Fires immediately on creation
  • No access to previous value
  • Ideal when all reactive values in the callback should be watched

Composition Patterns

Composables: Reusable Logic

Extract and share component logic through composables (Vue’s equivalent of React hooks):

  • Defined outside components
  • Encapsulate reactive state and methods
  • Superior to mixins (avoids naming conflicts)
  • Can compose multiple composables together

Vue Router Integration

Vue Router provides composables for accessing route state:

  • useRoute() - Access current route
  • useRouter() - Navigate programmatically

Async Operations with Suspense (Experimental)

Handle async setup with <Suspense>:

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Avoiding Prop Drilling with Provide/Inject

Share data deeply nested components without passing through each level:

  • provide(key, value) - Offer data from parent
  • inject(key, defaultValue) - Consume data in descendants
  • Useful for avoiding intermediate component prop chains

TypeScript Support in Vue 3

Vue 3 provides excellent TypeScript support, with concepts explored in depth in the TypeScript with Vue.js 3 course

Getting Started

Add lang="ts" to your <script> tag for full TypeScript support.

Typing Reactive State

  • Use reactive() with type annotations: reactive<MyType>({})
  • Use as MyType for editor hints on ref unwrapped values

Typing Props and Events

Props definition:

defineProps<{ 
  prop1: string
}>()

Default values for props are still experimental.

Events:

  • Use consistent symbols for event names like @create for IDE autocomplete

Injection with Types

Create a typed injection key:

const injectionKey = Symbol as InjectionKey<MyType>

// In parent
provide(injectionKey, value)

// In child
const value = inject(injectionKey)

Further Reading

You also might like