vue-patterns
affaan-m/ECC
Ofrece patrones de la API de composición de Vue.js 3, arquitectura de componentes, buenas prácticas de reactividad, gestión de estado con Pinia, navegación con Vue Router y patrones de SSR de Nuxt para proyectos de Vue, Nuxt, Vite o Pinia.
...Expandir todoPatrones y buenas prácticas de Vue.js
Guía completa para el desarrollo con Vue.js 3 utilizando la API de composición (), que abarca el diseño de componentes, la reactividad, la gestión del estado, el enrutamiento, las pruebas y los patrones de SSR. Se incluyen indicaciones específicas para Nuxt en los casos en que difiere de Vue «puro».
Cuándo activarla
Activa esta habilidad cuando:
- El proyecto utilice Vue.js (cualquier versión), Nuxt, Vite + Vue o Pinia.
- El usuario pregunte sobre la arquitectura de componentes de Vue, los composables, la reactividad o la gestión del estado.
- Se revisen los componentes de un solo archivo de Vue (archivos
.vue). - Configurando Vue Router, los almacenes de Pinia o la configuración de Vite/Vitest.
- Se debata sobre el rendimiento, la seguridad o los patrones de SSR específicos de Vue.
1. Estructura del proyecto
Estructura recomendada (Feature-First)
src/
├── api/ # Definiciones de cliente API y puntos finales
├── assets/ # Recursos estáticos (imágenes, fuentes, iconos)
├── components/ # Componentes compartidos/reutilizables
│ ├── base/ # Primitivas básicas de la interfaz de usuario (botón, campo de entrada, modal)
│ └── features/ # Componentes compartidos específicos de cada función
├── composables/ # Lógica reutilizable de la API de composición
├── layouts/ # Diseños de página (opcional)
├── pages/ # Componentes de página a nivel de ruta
├── router/ # Configuración de Vue Router
├── stores/ # Almacenes de Pinia
├── types/ # Definiciones de tipos de TypeScript
├── utils/ # Funciones de utilidad puras
└── App.vue # Componente raíz
Nomenclatura de archivos
| Convención | Cuándo utilizar |
|---|---|
PascalCase.vue |
Todos los componentes (obligatorio según vue/multi-word-component-names) |
useCamelCase.ts |
Componibles |
camelCase.ts |
Utilidades, clientes de API, tipos |
Directorios con nombre en«kebab-case» |
Segmentos de ruta, carpetas de funcionalidades |
2. Arquitectura de componentes
Orden de los componentes en un único archivo
Presentación frente a contenedor
- Componentes contenedores: gestionan la obtención de datos, el estado y los efectos secundarios. Representan componentes de presentación.
- Componentes de presentación: reciben props y emiten eventos. Sin llamadas a la API ni acceso al almacén. Representación pura.
Buenas prácticas con los props
// Props basados en tipos con valores por defecto
interface Props {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
items: Item[];
}
const props = withDefaults(defineProps(), {
variant: "primary",
disabled: false,
});
- Indica siempre
el tipoy, cuando sea pertinente, si es obligatorio o si tiene un valorpor defecto. - Propiedades booleanas:
isXxx,hasXxx,canXxx. - Nunca modifiques las propiedades; en su lugar, emite eventos.
- Para el enlace v-model, utiliza
defineModel()(Vue 3.4+) omodelValue+update:modelValue.
Eventos
const emit = defineEmits<{
submit: [];
"update:modelValue": [value: string];
select: [id: string, index: number];
}>();
- Utiliza el formato «kebab-case» en las plantillas (
@update:model-value). - Utiliza camelCase en el script (
emit("update:modelValue", val)).
3. Composables (lógica reutilizable)
Estructura
// composables/useDebounce.ts
export function useDebounce(value: MaybeRef, delay: number): Ref {
const debounced = ref(toValue(value)) as Ref;
let timer: ReturnType;
watch(
() => toValue(valor),
(nuevoValor) => {
clearTimeout(temporizador);
temporizador = setTimeout(() => { debounced.valor = nuevoValor; }, retraso);
}
);
onUnmounted(() => clearTimeout(timer));
return readonly(debounced);
}
Reglas
- Debe comenzar con el prefijo
«use». - Debe devolver valores reactivos (
ref,computados,reactivos), nunca tipos primitivos simples. - Debe aceptar entradas reactivas a través de
`MaybeRef`, `toRef()` o `toValue()`. - Elimina los efectos secundarios en
onUnmountedo enonCleanupdel observador. - Sin efectos secundarios en el ámbito del módulo.
vs. Mixins
Los composables sustituyen por completo a los mixins de Vue 2:
- Mixins: flujo de datos opaco, colisiones en la «fuente de verdad», conflictos de nombres.
- Componibles: importaciones explícitas, valores de retorno claros, componibles y «tree-shakable».
4. Gestión del estado
Cuándo usar qué
| Patrón | Caso de uso |
|---|---|
ref() / reactive() |
Estado local del componente |
| Props + Emits | Comunicación entre padres e hijos |
| Proporcionar / Inyectar | API de temas, configuración y complementos |
| Almacén de Pinia | Estado global, compartido y complejo |
| Estado del servidor componible | Datos de la API con almacenamiento en caché (envolver fetch/TanStack Query) |
Configuración de Pinia Store (recomendado)
// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
const items = ref([]);
const isLoading = ref(false);
const totalPrice = computed(() =>
items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
);
const itemCount = computed(() =>
items.value.reduce((sum, i) => sum + i.quantity, 0)
);
async function addItem(productId: string) {
isLoading.value = true;
try {
const item = await fetchProduct(productId);
const existing = items.value.find(i => i.id === item.id);
if (existing) existing.quantity++;
else items.value.push({ ...item, quantity: 1 });
} finally {
isLoading.value = false;
}
}
return { items, isLoading, totalPrice, itemCount, addItem };
});
- Utiliza la sintaxis de Setup Store (no la de Options Store).
- Da prioridad a las acciones para las mutaciones a nivel empresarial y a
$patch()para las actualizaciones agrupadas. - Cada acción asíncrona: gestiona la carga, el éxito y el error.
5. Vue Router
Definiciones de rutas
const routes = [
{
path: "/users/:id",
name: "user-detail",
component: () => import("@/pages/UserDetail.vue"), // carga diferida
props: true, // pasar parámetros como props
meta: { requiresAuth: true },
},
];
Protectores de navegación
router.beforeEach((to, from) => {
const { isLoggedIn } = useAuthStore();
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
Parámetros de ruta reactivos
Cuando un componente permanece montado pero cambian los parámetros de ruta:
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));
6. Patrones de plantillas
Sintaxis de plantillas
Cargando...
Error: {{ error }}
{{ content }}
Contenido oculto/visible
{{ item.name }}
{{ item.name }}
7. Rendimiento
| Técnica | Cuándo utilizarlo |
|---|---|
v-memo |
Elementos de una lista que rara vez cambian |
v-once |
Contenido que se muestra una sola vez y permanece estático para siempre |
shallowRef() |
Estructuras de datos grandes sustituidas en su totalidad |
shallowReactive() |
Solo las propiedades de nivel superior son reactivas |
v-show en lugar de v-if |
Cambios frecuentes en la visibilidad |
|
Almacenar en caché las vistas con visibilidad alterna |
| Rutas diferidas | () => import(...) para rutas no críticas |
Suspense |
Carga asíncrona de componentes con alternativa |
8. Pruebas
Stack
- Vitest para pruebas unitarias y de componentes
- Vue Test Utils para el montaje y la interacción
- @pinia/testing para simular el store
- Playwright para pruebas de extremo a extremo
Patrón de pruebas de componentes
import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";
beforeEach(() => { setActivePinia(createPinia()); });
it("se renderiza y emite", async () => {
const wrapper = mount(UserCard, {
props: { user: { id: "1", name: "Alice" } },
});
expect(wrapper.text()).toContain("Alice");
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});
9. Patrones específicos de Nuxt
Importaciones automáticas
Nuxt importa automáticamente ref, computed, watch, useFetch, useAsyncData, etc. Úsalos directamente sin necesidad de importarlos. En proyectos que no sean de Nuxt, impórtalos siempre de forma explícita.
useAsyncData / useFetch
const { data: user, pending, error, refresh } = await useAsyncData(
"user", // clave única para el almacenamiento en caché
() => $fetch(`/api/users/${id}`),
);
const { data: posts } = await useFetch("/api/posts", {
query: { page: 1 },
key: "posts-page-1", // elimina duplicados en las solicitudes
});
Rutas del servidor
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
const { id } = await getValidatedRouterParams(event, z.object({
id: z.string().uuid(),
}).parse);
// ... recuperar y devolver
});
Configuración en tiempo de ejecución
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// solo para el servidor
apiSecret: "",
// público (expuesto al cliente)
public: {
apiBase: "https://api.example.com",
},
},
});
10. Nuevas API de Vue 3.5+
Descomposición de props reactivos
Vue 3.5 ha estabilizado la desestructuración de props reactivos: las variables desestructuradas de defineProps() son automáticamente reactivas:
// Vue 3.5+: las propiedades desestructuradas son reactivas (no es necesario usar toRefs)
const { count = 0, msg = "hello" } = defineProps<{
count?: number;
msg?: string;
}>();
// Limitación: no se puede vigilar directamente una propiedad desestructurada
watch(() => count, (newVal) => { ... }); // REQUISITO: se necesita un getter
useTemplateRef()
Sustituye las referencias simples con nombre coincidente por useTemplateRef() para las referencias de plantilla:
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef("input");
// «input» coincide con el atributo ref="input" de la plantilla, no con el nombre de la variable
Admite ID de ref dinámicos: useTemplateRef(dynamicRefId).
onWatcherCleanup()
API de limpieza de observadores que se puede importar globalmente (Vue 3.5+). Debe llamarse de forma sincrónica dentro de la llamada de retorno del observador:
import { watch, onWatcherCleanup } from "vue";
watch(userId, async (newId) => {
const controller = new AbortController();
onWatcherCleanup(() => controller.abort());
// ... recuperar con la señal
});
useId()
Generación de un identificador único estable para SSR para elementos de formulario y accesibilidad:
import { useId } from "vue";
const id = useId();
defer Teleport
permite teletransportarse a destinos renderizados en el mismo ciclo:
Contenido
Hidratación diferida (SSR)
defineAsyncComponent() ahora admite la estrategia de hidratación:
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
loader: () => import("./Comp.vue"),
hydrate: hydrateOnVisible(),
});
Antipatrones
| Antipatrón | Por qué está mal | La solución |
|---|---|---|
Desestructuración de defineProps() (Vue < 3.5) |
Captura una instantánea y pierde reactividad | Accede mediante props.xxx o utiliza toRefs() |
watch() en la propiedad desestructurada (Vue 3.5+) |
Error en tiempo de compilación: las propiedades desestructuradas no se pueden supervisar directamente | Utiliza un envoltorio getter: watch(() => count, ...) |
v-if + v-for en el mismo elemento |
Orden de ejecución ambiguo | Utiliza una matriz filtrada calculada |
v-for con clave = índice |
Estado incorrecto al reordenar | Utilizar ID de base de datos estables |
| Propiedades mutables | Incumple el flujo de datos unidireccional | Emite eventos o utiliza v-model |
v-html con contenido del usuario |
Vulnerabilidad XSS | Limpiar con DOMPurify |
| Mixins en Vue 3 | Opaco, propenso a colisiones | Sustituir por composables |
| Efectos secundarios en el ámbito del módulo en los composables | Compartidos entre instancias | Ámbito en onMounted + onUnmounted |
reactive() para el estado sustituible |
La sustitución rompe la reactividad | Utiliza ref() en su lugar |
| Watcher sin limpieza | Fugas de memoria, condiciones de carrera | Utiliza onCleanup u onWatcherCleanup() (Vue 3.5+) |
| API de opciones en el nuevo código de Vue 3 | El ecosistema se está pasando a la API de composición | Utiliza |
| ref simple para las referencias en plantillas | No admite ref dinámicas; la coincidencia de nombres es frágil | Utiliza useTemplateRef() (Vue 3.5+) |
Habilidades relacionadas
accesibilidad— ARIA, HTML semántico, gestión del focopatrones de front-end— Arquitectura de front-end multienmarcoTypeScript— Mejores prácticas de TypeScript aplicadas a proyectos de Vueestándares de programación— Estándares generales de calidad del código
---
name: vue-patterns
description: Provides Vue.js 3 Composition API patterns, component architecture, reactivity best practices, Pinia state management, Vue Router navigation, and Nuxt SSR patterns for Vue, Nuxt, Vite, or Pinia projects.
---
# Vue.js Patterns and Best Practices
Comprehensive guide for Vue.js 3 development using Composition API (`<script setup>`), covering component design, reactivity, state management, routing, testing, and SSR patterns. Nuxt-specific guidance is included where it differs from vanilla Vue.
## When to Activate
Activate this skill when:
- The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia.
- The user asks about Vue component architecture, composables, reactivity, or state management.
- Reviewing Vue Single-File Components (`.vue` files).
- Setting up Vue Router, Pinia stores, or Vite/Vitest configuration.
- Discussing Vue-specific performance, security, or SSR patterns.
---
## 1. Project Structure
### Recommended Layout (Feature-First)
```
src/
├── api/ # API client and endpoint definitions
├── assets/ # Static assets (images, fonts, icons)
├── components/ # Shared/reusable components
│ ├── base/ # Base UI primitives (Button, Input, Modal)
│ └── features/ # Feature-specific shared components
├── composables/ # Reusable Composition API logic
├── layouts/ # Page layouts (optional)
├── pages/ # Route-level page components
├── router/ # Vue Router configuration
├── stores/ # Pinia stores
├── types/ # TypeScript type definitions
├── utils/ # Pure utility functions
└── App.vue # Root component
```
### File Naming
| Convention | When to Use |
|-----------|-------------|
| `PascalCase.vue` | All components (enforced by `vue/multi-word-component-names`) |
| `useCamelCase.ts` | Composables |
| `camelCase.ts` | Utilities, API clients, types |
| `kebab-case` directories | Route segments, feature folders |
---
## 2. Component Architecture
### Single-File Component Order
```vue
<script setup lang="ts">
// 1. Imports (vue → ecosystem → absolute → relative)
// 2. Props & Emits & Slots
// 3. Composables
// 4. Local state (ref/reactive)
// 5. Computed properties
// 6. Methods
// 7. Watchers
// 8. Lifecycle hooks
</script>
<template>
<!-- Template content -->
</template>
<style scoped>
/* Scoped styles */
</style>
```
### Presentational vs Container
- **Container components**: Own data fetching, state, and side effects. Render presentational components.
- **Presentational components**: Receive props, emit events. No API calls, no store access. Pure rendering.
### Props Best Practices
```ts
// Type-based props with defaults
interface Props {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
items: Item[];
}
const props = withDefaults(defineProps<Props>(), {
variant: "primary",
disabled: false,
});
```
- Always provide `type`, and `required`/`default` where appropriate.
- Boolean props: `isXxx`, `hasXxx`, `canXxx`.
- Never mutate props — emit events instead.
- For v-model binding, use `defineModel()` (Vue 3.4+) or `modelValue` + `update:modelValue`.
### Events
```ts
const emit = defineEmits<{
submit: [];
"update:modelValue": [value: string];
select: [id: string, index: number];
}>();
```
- Use kebab-case in templates (`@update:model-value`).
- Use camelCase in script (`emit("update:modelValue", val)`).
---
## 3. Composables (Reusable Logic)
### Structure
```ts
// composables/useDebounce.ts
export function useDebounce<T>(value: MaybeRef<T>, delay: number): Ref<T> {
const debounced = ref(toValue(value)) as Ref<T>;
let timer: ReturnType<typeof setTimeout>;
watch(
() => toValue(value),
(newVal) => {
clearTimeout(timer);
timer = setTimeout(() => { debounced.value = newVal; }, delay);
}
);
onUnmounted(() => clearTimeout(timer));
return readonly(debounced);
}
```
### Rules
- Must start with `use` prefix.
- Return reactive values (`ref`, `computed`, `reactive`), never plain primitives.
- Accept reactive inputs via `MaybeRef` / `toRef()` / `toValue()`.
- Clean up side effects in `onUnmounted` or watcher `onCleanup`.
- No module-scope side effects.
### vs Mixins
Composables replace Vue 2 mixins entirely:
- **Mixins**: Opaque data flow, source-of-truth collisions, name conflicts.
- **Composables**: Explicit imports, clear return values, composable and tree-shakable.
---
## 4. State Management
### When to Use What
| Pattern | Use Case |
|---------|----------|
| `ref()` / `reactive()` | Local component state |
| Props + Emits | Parent-child communication |
| Provide / Inject | Theme, config, plugin API |
| Pinia store | Global, shared, complex state |
| Server state composable | API data with caching (wrap `fetch`/TanStack Query) |
### Pinia Setup Store (Preferred)
```ts
// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
const items = ref<CartItem[]>([]);
const isLoading = ref(false);
const totalPrice = computed(() =>
items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
);
const itemCount = computed(() =>
items.value.reduce((sum, i) => sum + i.quantity, 0)
);
async function addItem(productId: string) {
isLoading.value = true;
try {
const item = await fetchProduct(productId);
const existing = items.value.find(i => i.id === item.id);
if (existing) existing.quantity++;
else items.value.push({ ...item, quantity: 1 });
} finally {
isLoading.value = false;
}
}
return { items, isLoading, totalPrice, itemCount, addItem };
});
```
- Use Setup Store syntax (not Options Store).
- Prefer actions for business-level mutations and `$patch()` for grouped updates.
- Every async action: handle loading + success + error.
---
## 5. Vue Router
### Route Definitions
```ts
const routes = [
{
path: "/users/:id",
name: "user-detail",
component: () => import("@/pages/UserDetail.vue"), // lazy
props: true, // pass params as props
meta: { requiresAuth: true },
},
];
```
### Navigation Guards
```ts
router.beforeEach((to, from) => {
const { isLoggedIn } = useAuthStore();
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
```
### Reactive Route Params
When a component stays mounted but route params change:
```ts
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));
```
---
## 6. Template Patterns
### Template Syntax
```vue
<!-- v-if/v-else-if/v-else -->
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ content }}</div>
<!-- v-show for frequent toggles -->
<div v-show="isOpen">Toggled content</div>
<!-- v-for with stable keys -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
<!-- Computed filtered list (not v-if + v-for on same element) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>
<!-- Event handling -->
<form @submit.prevent="handleSubmit">
<button type="submit">Save</button>
</form>
<!-- v-model -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />
```
---
## 7. Performance
| Technique | When to Use |
|-----------|-------------|
| `v-memo` | List items that rarely change |
| `v-once` | Content rendered once and static forever |
| `shallowRef()` | Large data structures replaced wholesale |
| `shallowReactive()` | Only top-level properties are reactive |
| `v-show` over `v-if` | Frequent visibility toggles |
| `<KeepAlive :max="10">` | Cache toggled views |
| Lazy routes | `() => import(...)` for non-critical routes |
| `Suspense` | Async component loading with fallback |
---
## 8. Testing
### Stack
- **Vitest** for unit and component tests
- **Vue Test Utils** for mounting and interaction
- **@pinia/testing** for store mocking
- **Playwright** for E2E
### Component Test Pattern
```ts
import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";
beforeEach(() => { setActivePinia(createPinia()); });
it("renders and emits", async () => {
const wrapper = mount(UserCard, {
props: { user: { id: "1", name: "Alice" } },
});
expect(wrapper.text()).toContain("Alice");
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});
```
---
## 9. Nuxt-Specific Patterns
### Auto-Imports
Nuxt auto-imports `ref`, `computed`, `watch`, `useFetch`, `useAsyncData`, etc. Use them directly without importing. For non-Nuxt projects, always import explicitly.
### useAsyncData / useFetch
```ts
const { data: user, pending, error, refresh } = await useAsyncData(
"user", // unique key for caching
() => $fetch(`/api/users/${id}`),
);
const { data: posts } = await useFetch("/api/posts", {
query: { page: 1 },
key: "posts-page-1", // dedupes requests
});
```
### Server Routes
```ts
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
const { id } = await getValidatedRouterParams(event, z.object({
id: z.string().uuid(),
}).parse);
// ... fetch and return
});
```
### Runtime Config
```ts
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// server-only
apiSecret: "",
// public (exposed to client)
public: {
apiBase: "https://api.example.com",
},
},
});
```
---
## 10. Vue 3.5+ New APIs
### Reactive Props Destructure
Vue 3.5 stabilized reactive props destructure — destructured variables from `defineProps()` are automatically reactive:
```ts
// Vue 3.5+: destructured props are reactive (no need for toRefs)
const { count = 0, msg = "hello" } = defineProps<{
count?: number;
msg?: string;
}>();
// Limitation: cannot watch destructured prop directly
watch(() => count, (newVal) => { ... }); // PASS getter required
```
### `useTemplateRef()`
Replace name-matched plain refs with `useTemplateRef()` for template references:
```ts
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input" matches the ref="input" attribute in template, not the variable name
```
Supports dynamic ref IDs: `useTemplateRef(dynamicRefId)`.
### `onWatcherCleanup()`
Globally importable watcher cleanup API (Vue 3.5+). It must be called synchronously inside the watcher callback:
```ts
import { watch, onWatcherCleanup } from "vue";
watch(userId, async (newId) => {
const controller = new AbortController();
onWatcherCleanup(() => controller.abort());
// ... fetch with signal
});
```
### `useId()`
SSR-stable unique ID generation for form elements and accessibility:
```ts
import { useId } from "vue";
const id = useId();
```
### `defer` Teleport
`<Teleport defer>` allows teleporting to targets rendered in the same cycle:
```vue
<Teleport defer to="#container">Content</Teleport>
<div id="container"></div>
```
### Lazy Hydration (SSR)
`defineAsyncComponent()` now supports `hydrate` strategy:
```ts
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
loader: () => import("./Comp.vue"),
hydrate: hydrateOnVisible(),
});
```
---
## Anti-Patterns
| Anti-Pattern | Why It's Wrong | The Fix |
|-------------|---------------|---------|
| Destructuring `defineProps()` (Vue < 3.5) | Captures snapshot, loses reactivity | Access via `props.xxx` or use `toRefs()` |
| `watch()` on destructured prop (Vue 3.5+) | Compile-time error — destructured props can't be watched directly | Use getter wrapper: `watch(() => count, ...)` |
| `v-if` + `v-for` on same element | Ambiguous execution order | Use computed filtered array |
| `v-for` key = index | Broken state on reorder | Use stable database IDs |
| Mutating props | Violates one-way data flow | Emit events or use `v-model` |
| `v-html` with user content | XSS vulnerability | Sanitize with DOMPurify |
| Mixins in Vue 3 | Opaque, collision-prone | Replace with composables |
| Module-scope side effects in composable | Shared across instances | Scope in `onMounted` + `onUnmounted` |
| `reactive()` for replaceable state | Replacement breaks reactivity | Use `ref()` instead |
| Watcher without cleanup | Memory leaks, race conditions | Use `onCleanup` or `onWatcherCleanup()` (Vue 3.5+) |
| Options API in new Vue 3 code | Ecosystem move to Composition API | Use `<script setup>` |
| Plain ref for template references | No dynamic ref support, name-matching fragile | Use `useTemplateRef()` (Vue 3.5+) |
## Related Skills
- `accessibility` — ARIA, semantic HTML, focus management
- `frontend-patterns` — Cross-framework frontend architecture
- `typescript` — TypeScript best practices applied to Vue projects
- `coding-standards` — General code quality standards
Todos los archivos
1 archivosInstalar vue-patterns
Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.
Descargar ZIPClona el repositorio y copia los archivos de la habilidad a tu proyecto.
git clone https://github.com/affaan-m/ECC/tree/main/skills/vue-patterns # Copy SKILL.md to your .claude/skills/ directory
Copiar





Hogar
