vue-expert

Expert-level Vue 3 patterns, Composition API, state management, and performance optimization.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "vue-expert" with this command: npx skills add nguyenthienthanh/aura-frog/nguyenthienthanh-aura-frog-vue-expert

Vue Expert Skill

Expert-level Vue 3 patterns, Composition API, state management, and performance optimization.

Auto-Detection

This skill activates when:

  • Working with .vue files

  • Using Vue 3 Composition API

  • Detected vue in package.json

  • Using Pinia or Nuxt

  1. Composition API Patterns

Script Setup (Preferred)

<!-- ✅ GOOD - script setup syntax --> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; import type { User } from '@/types';

// Props with defaults interface Props { user: User; showAvatar?: boolean; }

const props = withDefaults(defineProps<Props>(), { showAvatar: true, });

// Emits with types const emit = defineEmits<{ select: [user: User]; update: [id: string, data: Partial<User>]; }>();

// Reactive state const isLoading = ref(false); const items = ref<Item[]>([]);

// Computed const fullName = computed(() => ${props.user.firstName} ${props.user.lastName});

// Methods function handleSelect() { emit('select', props.user); }

// Lifecycle onMounted(async () => { isLoading.value = true; items.value = await fetchItems(); isLoading.value = false; }); </script>

Composables (Custom Hooks)

// ✅ GOOD - Reusable composable // composables/useUser.ts import { ref, computed } from 'vue'; import type { User } from '@/types';

export function useUser(userId: Ref<string>) { const user = ref<User | null>(null); const isLoading = ref(false); const error = ref<Error | null>(null);

const fullName = computed(() => { if (user.value == null) return ''; return ${user.value.firstName} ${user.value.lastName}; });

async function fetchUser() { isLoading.value = true; error.value = null; try { user.value = await api.getUser(userId.value); } catch (e) { error.value = e instanceof Error ? e : new Error('Unknown error'); } finally { isLoading.value = false; } }

watch(userId, fetchUser, { immediate: true });

return { user: readonly(user), fullName, isLoading: readonly(isLoading), error: readonly(error), refetch: fetchUser, }; }

// Usage const userId = ref('123'); const { user, fullName, isLoading } = useUser(userId);

  1. Reactivity Best Practices

ref vs reactive

// ✅ GOOD - ref for primitives const count = ref(0); const name = ref(''); const isActive = ref(false);

// ✅ GOOD - ref for objects (consistent .value) const user = ref<User | null>(null); user.value = { id: '1', name: 'John' };

// ⚠️ CAUTION - reactive loses reactivity on reassignment const state = reactive({ user: null }); state.user = newUser; // ✅ Works // state = { user: newUser }; // ❌ Loses reactivity!

// ✅ GOOD - Use ref for replaceable objects const user = ref<User | null>(null); user.value = newUser; // ✅ Works

Watch Patterns

// ✅ GOOD - Watch single ref watch(userId, async (newId, oldId) => { if (newId !== oldId) { await fetchUser(newId); } });

// ✅ GOOD - Watch multiple sources watch( [userId, companyId], async ([newUserId, newCompanyId]) => { await fetchData(newUserId, newCompanyId); } );

// ✅ GOOD - watchEffect for auto-tracking watchEffect(async () => { // Automatically tracks userId.value const data = await fetchUser(userId.value); user.value = data; });

// ✅ GOOD - Cleanup in watch watch(userId, async (newId, oldId, onCleanup) => { const controller = new AbortController(); onCleanup(() => controller.abort());

const data = await fetchUser(newId, { signal: controller.signal }); user.value = data; });

  1. Template Best Practices

Conditional Rendering

<template> <!-- ❌ BAD - Implicit truthy check --> <div v-if="userName">{{ userName }}</div>

<!-- ✅ GOOD - Explicit check --> <div v-if="userName != null && userName !== ''">{{ userName }}</div>

<!-- ✅ GOOD - v-show for frequent toggles --> <div v-show="isVisible">Frequently toggled content</div>

<!-- ✅ GOOD - v-if for conditional rendering --> <div v-if="isLoaded">Rendered once</div>

<!-- ✅ GOOD - Template for multiple elements --> <template v-if="items.length > 0"> <h2>Items</h2> <ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul> </template> <EmptyState v-else /> </template>

List Rendering

<template> <!-- ❌ BAD - Index as key --> <li v-for="(item, index) in items" :key="index">{{ item.name }}</li>

<!-- ✅ GOOD - Unique ID as key --> <li v-for="item in items" :key="item.id">{{ item.name }}</li>

<!-- ✅ GOOD - v-for with v-if (separate element) --> <template v-for="item in items" :key="item.id"> <li v-if="item.isVisible">{{ item.name }}</li> </template>

<!-- ✅ GOOD - Computed for filtering --> <li v-for="item in visibleItems" :key="item.id">{{ item.name }}</li> </template>

<script setup lang="ts"> const visibleItems = computed(() => items.value.filter(item => item.isVisible)); </script>

Event Handling

<template> <!-- ✅ GOOD - Inline for simple --> <button @click="count++">Increment</button>

<!-- ✅ GOOD - Method reference --> <button @click="handleClick">Click</button>

<!-- ✅ GOOD - With event --> <input @input="handleInput($event)" />

<!-- ✅ GOOD - Modifiers --> <form @submit.prevent="handleSubmit"> <input @keyup.enter="submit" /> <div @click.stop="handleClick"> </template>

  1. Component Design

Props Validation

<script setup lang="ts"> // ✅ GOOD - Type-based props interface Props { // Required id: string; user: User;

// Optional with type size?: 'sm' | 'md' | 'lg'; disabled?: boolean;

// With default (use withDefaults) variant?: 'primary' | 'secondary'; }

const props = withDefaults(defineProps<Props>(), { size: 'md', disabled: false, variant: 'primary', }); </script>

Slots

<!-- ParentComponent.vue --> <template> <Card> <template #header> <h2>Title</h2> </template>

&#x3C;template #default>
  &#x3C;p>Main content&#x3C;/p>
&#x3C;/template>

&#x3C;template #footer="{ canSubmit }">
  &#x3C;button :disabled="!canSubmit">Submit&#x3C;/button>
&#x3C;/template>

</Card> </template>

<!-- Card.vue --> <template> <div class="card"> <header v-if="$slots.header"> <slot name="header" /> </header>

&#x3C;main>
  &#x3C;slot />
&#x3C;/main>

&#x3C;footer v-if="$slots.footer">
  &#x3C;slot name="footer" :canSubmit="isValid" />
&#x3C;/footer>

</div> </template>

Expose

<!-- ✅ GOOD - Expose specific methods/refs --> <script setup lang="ts"> const inputRef = ref<HTMLInputElement | null>(null);

function focus() { inputRef.value?.focus(); }

function reset() { // Reset logic }

// Only expose what's needed defineExpose({ focus, reset, }); </script>

  1. State Management (Pinia)

Store Definition

// stores/user.ts import { defineStore } from 'pinia';

// ✅ GOOD - Setup store syntax (Composition API style) export const useUserStore = defineStore('user', () => { // State const user = ref<User | null>(null); const isLoading = ref(false);

// Getters const isAuthenticated = computed(() => user.value != null); const fullName = computed(() => { if (user.value == null) return ''; return ${user.value.firstName} ${user.value.lastName}; });

// Actions async function login(credentials: Credentials) { isLoading.value = true; try { user.value = await authApi.login(credentials); } finally { isLoading.value = false; } }

function logout() { user.value = null; }

return { // State user: readonly(user), isLoading: readonly(isLoading), // Getters isAuthenticated, fullName, // Actions login, logout, }; });

Store Usage

<script setup lang="ts"> import { useUserStore } from '@/stores/user'; import { storeToRefs } from 'pinia';

const userStore = useUserStore();

// ✅ GOOD - storeToRefs for reactive destructuring const { user, isAuthenticated, fullName } = storeToRefs(userStore);

// Actions don't need storeToRefs const { login, logout } = userStore; </script>

  1. Performance Optimization

Computed Caching

// ✅ GOOD - Computed for derived state (cached) const sortedItems = computed(() => { return [...items.value].sort((a, b) => a.name.localeCompare(b.name)); });

// ❌ BAD - Method called in template (no caching) function getSortedItems() { return [...items.value].sort((a, b) => a.name.localeCompare(b.name)); }

v-once and v-memo

<template> <!-- ✅ GOOD - Static content --> <footer v-once> <p>Copyright 2024</p> </footer>

<!-- ✅ GOOD - Memoize expensive renders --> <div v-for="item in items" :key="item.id" v-memo="[item.id, item.selected]"> <ExpensiveComponent :item="item" /> </div> </template>

Async Components

// ✅ GOOD - Lazy load components const HeavyComponent = defineAsyncComponent(() => import('./components/HeavyComponent.vue') );

// ✅ GOOD - With loading/error states const AsyncModal = defineAsyncComponent({ loader: () => import('./Modal.vue'), loadingComponent: LoadingSpinner, errorComponent: ErrorDisplay, delay: 200, timeout: 3000, });

  1. Form Handling

VeeValidate + Zod

<script setup lang="ts"> import { useForm } from 'vee-validate'; import { toTypedSchema } from '@vee-validate/zod'; import { z } from 'zod';

const schema = toTypedSchema( z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Min 8 characters'), }) );

const { handleSubmit, errors, defineField } = useForm({ validationSchema: schema, });

const [email, emailAttrs] = defineField('email'); const [password, passwordAttrs] = defineField('password');

const onSubmit = handleSubmit(async (values) => { await login(values); }); </script>

<template> <form @submit="onSubmit"> <input v-model="email" v-bind="emailAttrs" type="email" /> <span v-if="errors.email">{{ errors.email }}</span>

&#x3C;input v-model="password" v-bind="passwordAttrs" type="password" />
&#x3C;span v-if="errors.password">{{ errors.password }}&#x3C;/span>

&#x3C;button type="submit">Login&#x3C;/button>

</form> </template>

  1. TypeScript Integration

Component Types

// ✅ GOOD - Import component type import type { Component } from 'vue'; import MyComponent from './MyComponent.vue';

// ✅ GOOD - Template ref typing const modalRef = ref<InstanceType<typeof MyComponent> | null>(null);

// ✅ GOOD - Global component types (env.d.ts) declare module 'vue' { export interface GlobalComponents { RouterLink: typeof import('vue-router')['RouterLink']; RouterView: typeof import('vue-router')['RouterView']; } }

Quick Reference

checklist[12]{pattern,best_practice}: Script,Use script setup lang="ts" State,ref for primitives and objects Props,withDefaults + defineProps<Props>() Emits,defineEmits with typed events Computed,Use for derived reactive state Watch,Use onCleanup for async Templates,Explicit v-if checks Keys,Unique IDs not indices Store,Pinia setup store syntax Store refs,storeToRefs for destructuring Async,defineAsyncComponent for lazy load Forms,VeeValidate + Zod

Version: 1.3.0

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

stitch-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

angular-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

visual-pixel-perfect

No summary provided by upstream source.

Repository SourceNeeds Review
General

nativewind-generator

No summary provided by upstream source.

Repository SourceNeeds Review