---
name: vue-composition-api
user-invocable: false
description: Use when Vue 3 Composition API with reactive refs, computed, and composables. Use when building modern Vue 3 applications.
allowed-tools:
- Bash
- Read
---
# Vue Composition API
Master the Vue 3 Composition API for building scalable, maintainable
Vue applications with better code organization and reusability.
## Setup Function Fundamentals
The `setup()` function is the entry point for using the Composition API:
```typescript
import { ref, computed, onMounted } from 'vue';
export default {
props: ['initialCount'],
setup(props, context) {
// props is reactive
console.log(props.initialCount);
// context provides attrs, slots, emit, expose
const { attrs, slots, emit, expose } = context;
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
emit('update', count.value);
}
onMounted(() => {
console.log('Component mounted');
});
// Expose public methods
expose({ increment });
// Return values to template
return {
count,
doubled,
increment
};
}
};
```
## Script Setup Syntax
Modern Vue 3 uses `
Count: {{ count }}
Doubled: {{ doubled }}
```
## Ref vs Reactive - When to Use Each
### Use Ref For
```typescript
import { ref } from 'vue';
// Primitives
const count = ref(0);
const name = ref('John');
const isActive = ref(true);
// Single object that needs replacement
const user = ref({ name: 'John', age: 30 });
user.value = { name: 'Jane', age: 25 }; // Works
// Arrays that need replacement
const items = ref([1, 2, 3]);
items.value = [4, 5, 6]; // Works
```
### Use Reactive For
```typescript
import { reactive, toRefs } from 'vue';
// Complex nested objects
const state = reactive({
user: { name: 'John', age: 30 },
settings: { theme: 'dark', notifications: true },
posts: []
});
// Group related state
const formState = reactive({
name: '',
email: '',
password: '',
errors: {}
});
// Convert to refs for destructuring
const { name, email } = toRefs(formState);
```
### Avoid Reactive For
```typescript
// DON'T: Replacing entire reactive object loses reactivity
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // Breaks reactivity!
// DO: Use ref instead
const state = ref({ count: 0 });
state.value = { count: 1 }; // Works
```
## Computed Properties Patterns
### Basic Computed
```typescript
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
```
### Writable Computed
```typescript
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
const names = value.split(' ');
firstName.value = names[0] || '';
lastName.value = names[1] || '';
}
});
// Can now set
fullName.value = 'Jane Smith';
```
### Computed with Complex Logic
```typescript
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
const cart = ref([]);
const cartSummary = computed(() => {
const total = cart.value.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const itemCount = cart.value.reduce((sum, item) =>
sum + item.quantity, 0
);
const tax = total * 0.08;
const grandTotal = total + tax;
return {
total,
itemCount,
tax,
grandTotal
};
});
```
## Watch and WatchEffect
### Watch - Explicit Dependencies
```typescript
import { ref, watch } from 'vue';
const count = ref(0);
const name = ref('');
// Watch single source
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// Watch multiple sources
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
console.log('Multiple values changed');
}
);
// Watch reactive object property
const user = reactive({ name: 'John', age: 30 });
watch(
() => user.name,
(newName) => {
console.log(`Name changed to ${newName}`);
}
);
// Deep watch
watch(
user,
(newUser) => {
console.log('User changed:', newUser);
},
{ deep: true }
);
```
### WatchEffect - Auto Tracking
```typescript
import { ref, watchEffect } from 'vue';
const count = ref(0);
const multiplier = ref(2);
// Automatically tracks dependencies
watchEffect(() => {
console.log(`Result: ${count.value * multiplier.value}`);
});
// Runs immediately and whenever dependencies change
```
### Advanced Watch Options
```typescript
const data = ref(null);
watch(
source,
(newValue, oldValue) => {
// Callback logic
},
{
immediate: true, // Run immediately
deep: true, // Deep watch objects
flush: 'post', // Timing: 'pre' | 'post' | 'sync'
onTrack(e) { // Debug
console.log('tracked', e);
},
onTrigger(e) { // Debug
console.log('triggered', e);
}
}
);
// Stop watching
const stop = watch(source, callback);
stop(); // Cleanup
```
## Lifecycle Hooks in Composition API
```typescript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
console.log('Before mount');
});
onMounted(() => {
console.log('Mounted');
// DOM is available
// Setup event listeners, fetch data
});
onBeforeUpdate(() => {
console.log('Before update');
});
onUpdated(() => {
console.log('Updated');
// DOM has been updated
});
onBeforeUnmount(() => {
console.log('Before unmount');
// Cleanup before unmount
});
onUnmounted(() => {
console.log('Unmounted');
// Final cleanup
});
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err, info);
return false; // Stop propagation
});
// For components wrapped in
onActivated(() => {
console.log('Component activated');
});
onDeactivated(() => {
console.log('Component deactivated');
});
}
};
```
## Composables - Reusable Composition Functions
### Simple Composable
```typescript
// composables/useCounter.ts
import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function reset() {
count.value = initialValue;
}
return {
count: readonly(count),
doubled,
increment,
decrement,
reset
};
}
// Usage
```
### Advanced Composable with Side Effects
```typescript
// composables/useFetch.ts
import { ref, unref, watchEffect } from 'vue';
import type { Ref } from 'vue';
export function useFetch(url: Ref | string) {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch(unref(url));
if (!response.ok) throw new Error('Fetch failed');
data.value = await response.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
watchEffect(() => {
fetchData();
});
return {
data: readonly(data),
error: readonly(error),
loading: readonly(loading),
refetch: fetchData
};
}
// Usage
```
### Composable with Cleanup
```typescript
// composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue';
export function useEventListener(
target: EventTarget,
event: string,
handler: (e: Event) => void
) {
onMounted(() => {
target.addEventListener(event, handler);
});
onUnmounted(() => {
target.removeEventListener(event, handler);
});
}
// Usage
```
## Props and Emits in Composition API
### TypeScript Props
```typescript
```
### TypeScript Emits
```typescript
```
### Runtime Props Validation
```typescript
```
## Provide and Inject Patterns
### Basic Provide/Inject
```typescript
```
### Type-Safe Provide/Inject
```typescript
// keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Ref;
updateTheme: (theme: string) => void;
}
export const ThemeKey: InjectionKey =
Symbol('theme');
// Provider
// Consumer
```
### Provide with Default Values
```typescript
```
## TypeScript with Composition API
### Component with Full Types
```typescript
```
### Generic Composables
```typescript
// composables/useLocalStorage.ts
import { ref, watch, type Ref } from 'vue';
export function useLocalStorage(
key: string,
defaultValue: T
): Ref {
const data = ref(defaultValue) as Ref;
// Load from localStorage
const stored = localStorage.getItem(key);
if (stored) {
try {
data.value = JSON.parse(stored);
} catch (e) {
console.error('Failed to parse localStorage', e);
}
}
// Save to localStorage on change
watch(
data,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return data;
}
// Usage
const user = useLocalStorage('user', { id: 0, name: '' });
```
## When to Use This Skill
Use vue-composition-api when building modern, production-ready
applications that require:
- Complex component logic that benefits from better organization
- Reusable logic across multiple components (composables)
- Better TypeScript integration and type inference
- Fine-grained reactivity control
- Large-scale applications requiring maintainability
- Migration from Vue 2 Options API to Vue 3
- Sharing stateful logic without mixins
## Vue-Specific Best Practices
1. **Prefer `
```
### Async Data Loading
```typescript
Loading...
Error: {{ error }}
```
## Resources
- [Vue 3 Composition API Documentation](https://vuejs.org/guide/extras/composition-api-faq.html)
- [Composition API RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
- [VueUse - Collection of Composables](https://vueuse.org/)
- [Vue 3 TypeScript Guide](https://vuejs.org/guide/typescript/overview.html)
- [Composables Best Practices](https://vuejs.org/guide/reusability/composables.html)