--- 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 ` ``` ## 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 ``` ## 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)