Back to Rules
Vue.js91% popularity

Vue 3 Composition API Patterns

Prefer Composition API with script setup for better TypeScript support and reusable logic patterns.

Evan YouUpdated Feb 14, 2024

Overview

The Vue 3 Composition API represents a fundamental shift in how Vue applications are structured, offering superior TypeScript integration and enabling patterns for logic reuse that weren't possible with the Options API. The script setup syntax is the recommended way to use Composition API as it provides both compile-time optimizations and reduced boilerplate. The core advantage of Composition API is the ability to colocate related logic regardless of what options (data, computed, methods) it uses. Previously, Vue components required scattering related logic across multiple options, making complex features difficult to maintain. With composables, you can extract and reuse stateful logic across components while maintaining reactivity. The reactive() and ref() primitives form the foundation of Vue's reactivity system. ref() is designed for primitive values and returns a reactive object with a .value property, while reactive() creates a reactive proxy for objects. Understanding when to use each is important for performance—reactive() is slightly more efficient for objects but cannot be destructured without losing reactivity, while refs work for any type but require .value access. Lifecycle hooks in Composition API use the on prefix followed by the hook name, making them easy to identify and enabling tree-shaking for unused hooks. The onMounted, onUpdated, and onUnmounted hooks replace the corresponding options API lifecycle methods. New hooks like onRenderTracked and onRenderTriggered provide debugging capabilities for reactivity dependencies. Computed properties use the computed() function and automatically track their dependencies, recalculating only when necessary.

Code Example

.vuerules
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { useAuth } from '@/composables/useAuth';
import { useTodos } from '@/composables/useTodos';

interface Props {
  userId: string;
  initialFilter?: 'all' | 'active' | 'completed';
}

const props = withDefaults(defineProps<Props>(), {
  initialFilter: 'all',
});

const emit = defineEmits<{
  (e: 'select', id: string): void;
  (e: 'update:title', value: string): void;
}>();

const { user, isAuthenticated } = useAuth();
const {
  todos,
  filteredTodos,
  addTodo,
  toggleTodo,
  deleteTodo,
  pendingCount,
} = useTodos(props.userId);

const newTodoTitle = ref('');
const filter = ref(props.initialFilter);

const displayTodos = computed(() => {
  return filter.value === 'all'
    ? todos.value
    : filteredTodos.value(filter.value);
});

async function handleAddTodo() {
  if (!newTodoTitle.value.trim()) return;

  await addTodo({
    title: newTodoTitle.value,
    userId: props.userId,
    completed: false,
  });

  newTodoTitle.value = '';
}

onMounted(() => {
  console.log('TodoList mounted for user:', props.userId);
});

watch(filter, (newFilter) => {
  emit('update:title', 'Todos - ' + newFilter);
});
</script>

<template>
  <div class="todo-list">
    <header class="header">
      <h1>Welcome, {{ user?.name || 'Developer' }}</h1>
      <span class="pending-count">{{ pendingCount }} tasks remaining</span>
    </header>

    <form @submit.prevent="handleAddTodo" class="add-form">
      <input
        v-model="newTodoTitle"
        type="text"
        placeholder="What needs to be done?"
        aria-label="New todo input"
      />
      <button type="submit" :disabled="!newTodoTitle.trim()">
        Add Task
      </button>
    </form>

    <div class="filters">
      <button
        v-for="f in ['all', 'active', 'completed'] as const"
        :key="f"
        :class="{ active: filter === f }"
        @click="filter = f"
      >
        {{ f }}
      </button>
    </div>

    <ul class="todos">
      <li
        v-for="todo in displayTodos"
        :key="todo.id"
        :class="{ completed: todo.completed }"
      >
        <input
          type="checkbox"
          :checked="todo.completed"
          @change="toggleTodo(todo.id)"
        />
        <span @click="emit('select', todo.id)">{{ todo.title }}</span>
        <button @click="deleteTodo(todo.id)" aria-label="Delete todo">
          ×
        </button>
      </li>
    </ul>
  </div>
</template>

More Vue.js Rules

VUE
89%

Vue 3 Pinia State Management

Implement centralized state management with Pinia for Vue 3 applications.

vue3piniastate-management
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { User, Project } from '@/types';
import { userApi } from '@/api...
Mar 1, 2024by Eduardo San Martin Morote
View Rule
VUE
85%

Vue 3 Routing with Vue Router 4

Implement navigation guards, lazy loading, and advanced routing patterns in Vue 3.

vue3vue-routerrouting
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router';
import { computed, onMounted } from 'vue';
import { useAuthStore } from '@/...
Feb 5, 2024by Eduardo San Martin Morote
View Rule
VUE
93%

Vue 3 Composables Pattern

Extract and reuse stateful logic with Vue 3 Composables using the Composition API.

vue3composablescomposition-api
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import { useLocalStorage } from './useLocalStorage';
import { useFetch } from './u...
Mar 18, 2024by Evan You
View Rule