opción
HogarHogar Skill Desarrollo web vue-patterns

vue-patterns

affaan-m/ECC 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 todo
0
Tiempo actualizado 2 de octubre de 2026

Patrones y buenas prácticas de Vue.js

Guía completa para el desarrollo con Vue.js 3 utilizando la API de composición (

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 tipo y, 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+) o modelValue + 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 onUnmounted o en onCleanup del 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

Todos los archivos

1 archivos

Instalar vue-patterns

Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.

Descargar ZIP

Clona 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 Copiar
Configuración rápida: Copia la carpeta de la habilidad en .claude/skills/ Claude detectará y utilizará automáticamente la habilidad
Repositorio affaan-m/ECC

Habilidades relacionadas

github-code-search
Tiempo actualizado 29 de junio de 2026
drizzle-orm
Tiempo actualizado 29 de junio de 2026
clickhouse-io
Tiempo actualizado 29 de junio de 2026
prisma-client-api
Tiempo actualizado 29 de junio de 2026