opção

vue-patterns

affaan-m/ECC affaan-m/ECC

Apresenta padrões da API de Composição do Vue.js 3, arquitetura de componentes, melhores práticas de reatividade, gerenciamento de estado com Pinia, navegação com o Vue Router e padrões de SSR do Nuxt para projetos em Vue, Nuxt, Vite ou Pinia.

...Expandir tudo
0
Tempo atualizado 2 de Outubro de 2026

Padrões e melhores práticas do Vue.js

Guia abrangente para o desenvolvimento com Vue.js 3 usando a Composition API (

Apresentação x Contêiner

  • Componentes contêineres: Possuem busca de dados, estado e efeitos colaterais próprios. Renderizam componentes de apresentação.
  • Componentes de apresentação: Recebem props, emitem eventos. Sem chamadas de API, sem acesso ao store. Renderização pura.

Melhores práticas para props

// Props baseados em tipos com valores padrão
interface Props {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  items: Item[];
}

const props = withDefaults(defineProps(), {
  variant: "primary",
  disabled: false,
});
  • Sempre forneça o tipo e os valoresobrigatórios/padrão quando apropriado.
  • Props booleanos: isXxx, hasXxx, canXxx.
  • Nunca altere as propriedades — em vez disso, emita eventos.
  • Para vinculação via v-model, use defineModel() (Vue 3.4+) ou modelValue + update:modelValue.

Eventos

const emit = defineEmits<{
  submit: [];
  "update:modelValue": [value: string];
  select: [id: string, index: number];
}>();
  • Use kebab-case nos modelos (@update:model-value).
  • Use camelCase no script (emit("update:modelValue", val)).

3. Composables (Lógica reutilizável)

Estrutura

// composables/useDebounce.ts
export function useDebounce(value: MaybeRef, delay: number): Ref {
  const debounced = ref(toValue(value)) as Ref;

  let timer: ReturnType;
  watch(
    () => toValue(valor),
    (novoVal) => {
      clearTimeout(timer);
      timer = setTimeout(() => { debounced.valor = novoVal; }, delay);
    }
  );

  onUnmounted(() => clearTimeout(timer));
  return readonly(debounced);
}

Regras

  • Deve começar com o prefixo ` use `.
  • Devem retornar valores reativos (ref, computed, reactive), nunca tipos primitivos simples.
  • Aceite entradas reativas via `MaybeRef ` / ` toRef() ` / ` toValue()`.
  • Limpe os efeitos colaterais em onUnmounted ou no onCleanup do observador.
  • Sem efeitos colaterais no escopo do módulo.

vs Mixins

Os composables substituem totalmente os mixins do Vue 2:

  • Mixins: fluxo de dados opaco, colisões de “fonte de verdade”, conflitos de nomes.
  • Composables: importações explícitas, valores de retorno claros, composíveis e otimizáveis por tree-shaking.

4. Gerenciamento de estado

Quando usar o quê

Padrão Caso de uso
ref() / reactive() Estado do componente local
Props + Emits Comunicação pai-filho
Fornecer / Injetar API de tema, configuração e plug-ins
Pinia Store Estado global, compartilhado e complexo
Estado do servidor composível Dados da API com cache (envolver fetch/TanStack Query)

Configuração da loja Pinia (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 };
});
  • Use a sintaxe do Setup Store (não do Options Store).
  • Dê preferência a ações para mutações no nível de negócios e a $patch() para atualizações agrupadas.
  • Em todas as ações assíncronas: lide com carregamento + sucesso + erro.

5. Vue Router

Definições de rotas

const routes = [
  {
    path: "/users/:id",
    name: "user-detail",
    component: () => import("@/pages/UserDetail.vue"), // carregamento diferido
    props: true, // passar parâmetros como props
    meta: { requiresAuth: true },
  },
];

Guards de navegação

router.beforeEach((to, from) => {
  const { isLoggedIn } = useAuthStore();
  if (to.meta.requiresAuth && !isLoggedIn) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});

Parâmetros de rota reativos

Quando um componente permanece montado, mas os parâmetros de rota mudam:

const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));

6. Padrões de modelo

Sintaxe de modelo


Carregando...
Erro: {{ error }}
{{ content }}
Conteúdo alternado
{{ item.name }}
{{ item.name }}

7. Desempenho

Técnica Quando usar
v-memo Itens de lista que raramente mudam
v-once Conteúdo renderizado uma vez e estático para sempre
shallowRef() Estruturas de dados grandes substituídas em sua totalidade
shallowReactive() Apenas as propriedades de nível superior são reativas
v-show em vez de v-if Alterações frequentes de visibilidade
Armazenar em cache as visualizações alternadas
Rotas preguiçosas () => import(...) para rotas não críticas
Suspense Carregamento assíncrono de componentes com fallback

8. Testes

Stack

  • Vitest para testes unitários e de componentes
  • Vue Test Utils para montagem e interação
  • @pinia/testing para simulação de store
  • Playwright para testes E2E

Padrão de teste de componentes

import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";

beforeEach(() => { setActivePinia(createPinia()); });

it("renderiza e 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. Padrões específicos do Nuxt

Importações automáticas

O Nuxt importa automaticamente ref, computed, watch, useFetch, useAsyncData, etc. Use-os diretamente sem precisar importá-los. Em projetos que não sejam do Nuxt, sempre importe-os explicitamente.

useAsyncData / useFetch

const { data: user, pending, error, refresh } = await useAsyncData(
  "user", // chave única para armazenamento em cache
  () => $fetch(`/api/users/${id}`),
);

const { data: posts } = await useFetch("/api/posts", {
  query: { page: 1 },
  key: "posts-page-1", // elimina solicitações duplicadas
});

Rotas do servidor

// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const { id } = await getValidatedRouterParams(event, z.object({
    id: z.string().uuid(),
  }).parse);
  // ... buscar e retornar
});

Configuração de tempo de execução

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // somente para o servidor
    apiSecret: "",
    // público (exposto ao cliente)
    public: {
      apiBase: "https://api.example.com",
    },
  },
});

10. Novas APIs do Vue 3.5+

Desestruturação de props reativos

O Vue 3.5 estabilizou a desestruturação de props reativos — variáveis desestruturadas a partir de ` defineProps()` tornam-se automaticamente reativas:

// Vue 3.5+: props desestruturados são reativos (não há necessidade de toRefs)
const { count = 0, msg = "hello" } = defineProps<{
  count?: number;
  msg?: string;
}>();

// Limitação: não é possível monitorar uma propriedade desestruturada diretamente
watch(() => count, (newVal) => { ... }); // NECESSITAM de um getter

useTemplateRef()

Substitua as referências simples com nomes correspondentes por useTemplateRef() para referências de template:

import { useTemplateRef } from "vue";
const inputEl = useTemplateRef("input");
// “input” corresponde ao atributo ref="input" no template, não ao nome da variável

Suporta IDs de referência dinâmicos: useTemplateRef(dynamicRefId).

onWatcherCleanup()

API de limpeza de observadores importável globalmente (Vue 3.5+). Deve ser chamada de forma síncrona dentro do callback do observador:

import { watch, onWatcherCleanup } from "vue";

watch(userId, async (newId) => {
  const controller = new AbortController();
  onWatcherCleanup(() => controller.abort());
  // ... fazer a busca com o sinal
});

useId()

Geração de ID único estável para SSR para elementos de formulário e acessibilidade:

import { useId } from "vue";
const id = useId();

defer Teleport

permite o teletransporte para destinos renderizados no mesmo ciclo:

Conteúdo

Hidratação Preguiçosa (SSR)

defineAsyncComponent() agora suporta a estratégia de hidratação:

import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  hydrate: hydrateOnVisible(),
});

Anti-padrões

Antipadrão Por que isso está errado A correção
Desestruturação de defineProps() (Vue < 3.5) Captura um instantâneo, perde a reatividade Acesse via props.xxx ou use toRefs()
watch() na propriedade desestruturada (Vue 3.5+) Erro em tempo de compilação — props desestruturados não podem ser monitorados diretamente Use um wrapper de getter: watch(() => count, ...)
v-if + v-for no mesmo elemento Ordem de execução ambígua Use um array filtrado calculado
v-for key = índice Estado corrompido ao reordenar Use IDs estáveis do banco de dados
Propriedades mutáveis Viola o fluxo de dados unidirecional Emita eventos ou use v-model
v-html com conteúdo do usuário Vulnerabilidade a XSS Sanitize com o DOMPurify
Mixins no Vue 3 Opaco, propenso a colisões Substituir por composáveis
Efeitos colaterais no escopo do módulo em composables Compartilhado entre instâncias Escopo em onMounted + onUnmounted
reactive() para estado substituível A substituição interrompe a reatividade Use ref() em vez disso
Watcher sem limpeza Vazamentos de memória, condições de corrida Use onCleanup ou onWatcherCleanup() (Vue 3.5+)
API de opções no novo código do Vue 3 Mudança do ecossistema para a API de Composição Use

Todos os arquivos

1 arquivos

Instalar vue-patterns

Baixe e descompacte os arquivos de habilidades no diretório .claude/skills/.

Baixar ZIP

Clone o repositório e copie os arquivos da habilidade para o seu projeto.

git clone https://github.com/affaan-m/ECC/tree/main/skills/vue-patterns # Copy SKILL.md to your .claude/skills/ directory

Copiar Copiar
Configuração rápida: Copie a pasta da habilidade para .claude/skills/ O Claude detectará e utilizará automaticamente a habilidade
Repositório affaan-m/ECC

Habilidades relacionadas

github-code-search
Tempo atualizado 29 de Junho de 2026
drizzle-orm
Tempo atualizado 29 de Junho de 2026
clickhouse-io
Tempo atualizado 29 de Junho de 2026
prisma-client-api
Tempo atualizado 29 de Junho de 2026