Use Cases (Atualizado: 02/06/2026)

Claude Code para Vue 3: guia prático com TypeScript e Pinia

Use Claude Code com Vue 3, TypeScript, formulários, Pinia, composables e Vitest em fluxo real de projeto.

Claude Code para Vue 3: guia prático com TypeScript e Pinia

Claude Code traz mais valor em Vue 3 quando é usado para trabalhar dentro de um repositório real, não apenas para gerar um componente isolado. Em projetos de produção, o trabalho importante costuma ser ler um SFC existente, preservar os tipos TypeScript, extrair lógica repetida para um composable, mover estado compartilhado para Pinia e adicionar testes Vitest antes de publicar.

Vue é flexível, e essa flexibilidade também cria riscos. Um projeto pode misturar Options API, Composition API, script setup, Pinia, hábitos antigos de Vuex, convenções de Nuxt e helpers locais. Se o prompt for apenas “crie um formulário em Vue”, Claude Code pode entregar algo que funciona, mas que não respeita a arquitetura do time.

Este guia mostra Claude Code + Vue 3 + TypeScript em cinco situações práticas: melhorar Vue existente, implementar formulário, estruturar estado com Pinia, extrair composable e adicionar testes com Vitest. Para base técnica, consulte a documentação oficial de Vue TypeScript com Composition API, Pinia e Vitest. Como leitura complementar, veja TypeScript tips e estratégia de testes.

Premissas do projeto

O fluxo considera Vue 3, TypeScript, Vite, Pinia e Vitest. Ele combina com painéis internos, help desks, sistemas de reserva, aprovações internas e áreas administrativas. Nesses produtos, a complexidade aparece em formulários, listas, filtros, paginação, estado compartilhado e testes.

Antes de pedir código, dê a Claude Code um harness, ou seja, uma base segura para o agente trabalhar. Esse harness inclui arquivos permitidos, convenções, proibições, comandos de verificação e pontos de revisão. Sem isso, o agente pode resolver o problema local adicionando any, alterando textos da UI ou movendo lógica para a camada errada.

SituaçãoTarefa boa para Claude CodeDecisão humana
Refatorar Vue existenteLer SFC, achar duplicação, reforçar tiposCompatibilidade visual e risco
FormulárioEstado local, validação, envioMensagens e contrato de API
PiniaTipos, getters, actions, testesPersistência e limites
ComposableFiltros, paginação, reutilizaçãoSe há reutilização real
TestesCasos normais, erros, regressãoNível de cobertura

Visão de arquitetura

Quando o exemplo cresce, a pergunta principal não é se Claude Code consegue escrever o código. A pergunta é onde cada responsabilidade deve ficar. Fronteiras claras tornam o diff gerado muito mais fácil de revisar.

flowchart LR
  A[Vue SFC] --> B[Composable]
  A --> C[Pinia Store]
  B --> D[Vitest]
  C --> D
  E[Claude Code Prompt] --> A
  E --> B
  E --> C
  E --> D

O SFC fica com a tela e os eventos do usuário. O composable contém lógica reativa reutilizável, como filtros e paginação. O store Pinia guarda estado compartilhado. Vitest protege o comportamento que não pode quebrar.

Criar a base Vue 3 + TypeScript

Para um experimento novo, comece pelo scaffolding oficial de Vue. Em um repositório existente, confirme primeiro que typecheck e testes passam; assim você avalia o impacto de Claude Code sobre uma base limpa.

npm create vue@latest support-desk-vue -- --typescript --router --pinia --vitest
cd support-desk-vue
npm install
npm run dev

Não escreva apenas “faça um app Vue moderno”. Informe gerenciador de pacotes, Vue Router, Pinia e comando de teste. Em uma aplicação real, peça para Claude Code ler package.json, vite.config.ts, tsconfig.app.json, src/components e src/stores antes de editar.

Caso 1: formulário SFC tipado

Formulários são bons para começar porque juntam v-model, props, emit, validação, estado de salvamento e actions do store. O exemplo abaixo pode ficar em src/components/TicketForm.vue. Ele não muta props, mantém valores editáveis em refs locais e emite o id após salvar.

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useTicketStore, type TicketPriority } from '@/stores/ticketStore';

const props = withDefaults(
  defineProps<{
    initialTitle?: string;
    defaultPriority?: TicketPriority;
  }>(),
  {
    initialTitle: '',
    defaultPriority: 'medium',
  },
);

const emit = defineEmits<{
  submitted: [id: string];
}>();

const store = useTicketStore();
const title = ref(props.initialTitle);
const description = ref('');
const priority = ref<TicketPriority>(props.defaultPriority);
const touched = ref(false);

const titleError = computed(() => {
  if (!touched.value) return '';
  if (title.value.trim().length < 5) return 'Use at least 5 characters.';
  return '';
});

const descriptionError = computed(() => {
  if (!touched.value) return '';
  if (description.value.trim().length < 20) return 'Use at least 20 characters.';
  return '';
});

const canSubmit = computed(() => {
  return (
    title.value.trim().length >= 5 &&
    description.value.trim().length >= 20 &&
    !store.isSaving
  );
});

async function submit() {
  touched.value = true;
  if (!canSubmit.value) return;

  const ticket = await store.saveTicket({
    title: title.value.trim(),
    description: description.value.trim(),
    priority: priority.value,
  });

  title.value = '';
  description.value = '';
  priority.value = props.defaultPriority;
  touched.value = false;
  emit('submitted', ticket.id);
}
</script>

<template>
  <form class="ticket-form" @submit.prevent="submit">
    <label>
      Title
      <input v-model="title" name="title" @blur="touched = true" />
    </label>
    <p v-if="titleError" class="error">{{ titleError }}</p>

    <label>
      Description
      <textarea v-model="description" name="description" @blur="touched = true" />
    </label>
    <p v-if="descriptionError" class="error">{{ descriptionError }}</p>

    <label>
      Priority
      <select v-model="priority" name="priority">
        <option value="low">Low</option>
        <option value="medium">Medium</option>
        <option value="high">High</option>
      </select>
    </label>

    <button type="submit" :disabled="!canSubmit">
      {{ store.isSaving ? 'Saving...' : 'Create ticket' }}
    </button>
  </form>
</template>

Ao pedir esse componente, inclua restrições: não mutar props, não introduzir any, não adicionar biblioteca visual e não colocar detalhes de API no SFC. Restrições negativas evitam muita limpeza manual.

Caso 2: estado compartilhado com Pinia

Se várias telas usam a lista de tickets, o formulário não deve possuir todo o estado. Pinia concentra lista, flag de salvamento, getters e actions em uma camada clara.

import { computed, ref } from 'vue';
import { defineStore } from 'pinia';

export type TicketPriority = 'low' | 'medium' | 'high';
export type TicketStatus = 'open' | 'closed';

export interface Ticket {
  id: string;
  title: string;
  description: string;
  priority: TicketPriority;
  status: TicketStatus;
  createdAt: string;
}

export type NewTicketInput = Pick<Ticket, 'title' | 'description' | 'priority'>;

export const useTicketStore = defineStore('tickets', () => {
  const tickets = ref<Ticket[]>([]);
  const isSaving = ref(false);

  const openTickets = computed(() => {
    return tickets.value.filter((ticket) => ticket.status === 'open');
  });

  function addTicket(input: NewTicketInput) {
    const ticket: Ticket = {
      id: crypto.randomUUID(),
      ...input,
      status: 'open',
      createdAt: new Date().toISOString(),
    };

    tickets.value.unshift(ticket);
    return ticket;
  }

  async function saveTicket(input: NewTicketInput) {
    isSaving.value = true;
    try {
      await new Promise((resolve) => setTimeout(resolve, 150));
      return addTicket(input);
    } finally {
      isSaving.value = false;
    }
  }

  function closeTicket(id: string) {
    const ticket = tickets.value.find((item) => item.id === id);
    if (ticket) ticket.status = 'closed';
  }

  return {
    tickets,
    isSaving,
    openTickets,
    addTicket,
    saveTicket,
    closeTicket,
  };
});

Em produção, saveTicket chamaria um cliente de API tipado. Mesmo assim, revise primeiro o modelo do store, depois conecte rede e por fim adicione tratamento de erro. Cada diff fica menor.

Caso 3: extrair um composable

Composable é uma função reutilizável com lógica reativa de Vue. Em termos simples, é a lógica da tela separada da tela. Busca, filtro por prioridade e paginação são bons candidatos.

import { computed, ref, watch, type Ref } from 'vue';
import type { Ticket, TicketPriority } from '@/stores/ticketStore';

export function useFilteredTickets(tickets: Ref<Ticket[]>) {
  const query = ref('');
  const selectedPriority = ref<TicketPriority | 'all'>('all');
  const currentPage = ref(1);
  const pageSize = ref(10);

  const filteredTickets = computed(() => {
    const normalizedQuery = query.value.trim().toLowerCase();

    return tickets.value.filter((ticket) => {
      const matchesQuery =
        normalizedQuery.length === 0 ||
        ticket.title.toLowerCase().includes(normalizedQuery) ||
        ticket.description.toLowerCase().includes(normalizedQuery);

      const matchesPriority =
        selectedPriority.value === 'all' ||
        ticket.priority === selectedPriority.value;

      return matchesQuery && matchesPriority;
    });
  });

  const totalPages = computed(() => {
    return Math.max(1, Math.ceil(filteredTickets.value.length / pageSize.value));
  });

  const pagedTickets = computed(() => {
    const start = (currentPage.value - 1) * pageSize.value;
    return filteredTickets.value.slice(start, start + pageSize.value);
  });

  watch([query, selectedPriority], () => {
    currentPage.value = 1;
  });

  return {
    query,
    selectedPriority,
    currentPage,
    pageSize,
    filteredTickets,
    pagedTickets,
    totalPages,
  };
}

Aqui watch só reseta a página quando os filtros mudam. Valores derivados ficam em computed. Essa regra deve entrar no prompt: computed para derivação, watch para efeitos explícitos.

Caso 4: testes com Vitest

Quanto mais rápida fica a implementação, mais importantes são os testes de regressão. Comece pelo store, pois ele está perto do comportamento de negócio e é menos frágil que testes de DOM.

import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useTicketStore } from './ticketStore';

describe('useTicketStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    vi.stubGlobal('crypto', {
      randomUUID: () => 'ticket-1',
    });
  });

  it('adds a new open ticket', () => {
    const store = useTicketStore();

    const ticket = store.addTicket({
      title: 'Cannot submit expense form',
      description: 'The submit button stays disabled after all fields are filled.',
      priority: 'high',
    });

    expect(ticket.id).toBe('ticket-1');
    expect(store.tickets).toHaveLength(1);
    expect(store.openTickets).toHaveLength(1);
  });

  it('closes an existing ticket', () => {
    const store = useTicketStore();
    const ticket = store.addTicket({
      title: 'Profile image is broken',
      description: 'The image URL returns 404 on the account settings page.',
      priority: 'medium',
    });

    store.closeTicket(ticket.id);

    expect(store.openTickets).toHaveLength(0);
    expect(store.tickets[0]?.status).toBe('closed');
  });
});

Não peça apenas “adicione mais testes”. Peça comportamentos: entrada curta, entrada válida, estado de salvamento, sucesso, falha e mudança no estado Pinia.

Prompt para refatorar Vue existente

Em código existente, evite uma grande reescrita. Primeiro peça leitura e riscos, depois ajuste tipos, extraia um composable, organize Pinia se necessário e adicione testes.

You are working in a Vue 3 + TypeScript + Pinia project.

Goal:
Refactor the ticket form so validation logic is typed, reusable, and covered by Vitest.

Allowed files:
- src/components/TicketForm.vue
- src/composables/useFilteredTickets.ts
- src/stores/ticketStore.ts
- src/stores/ticketStore.test.ts

Rules:
- Use <script setup lang="ts">.
- Do not mutate props directly.
- Do not introduce any.
- Prefer computed for derived values.
- Use watch only for explicit side effects.
- Keep UI text unchanged unless a validation message is missing.

Verification:
- npm run typecheck
- npm run test:unit

After editing, explain the risk areas and the tests you added.

Esse prompt separa o que Claude Code pode propor do que o time precisa decidir. Extração, tipos e testes são bons trabalhos para a ferramenta. Texto de UI, contrato de API, acessibilidade e timing de release precisam de revisão humana.

Armadilhas comuns

A primeira armadilha é misturar Options API e Composition API sem plano. Vue 3 suporta as duas, mas um componente com data, methods, setup e script setup fica difícil de revisar.

A segunda é entender mal ref e reactive. Para valores primitivos, arrays e dados substituíveis, ref costuma ser mais simples. reactive funciona bem para objetos coesos, mas destructuring pode confundir.

A terceira é mutar props. Um filho não deve alterar silenciosamente estado do pai. Use ref local, emit ou defineModel quando o time já tiver adotado esse padrão.

A quarta é abusar de watch. Se um valor pode ser derivado, use computed. Guarde watch para sincronizar rota, resetar página, chamar API ou escrever em storage.

A quinta é deixar tipos caírem em any. Claude Code pode usar as any para contornar erro. Revise any, unknown, assertions e inferência de array vazio.

A sexta é dividir SFC demais. Muitos componentes pequenos sem reutilização real aumentam props e emits. Extraia lógica para composables primeiro e só divida UI quando houver independência.

A sétima é falta de teste. Código gerado pode parecer limpo e falhar em bordas. Em formulários, cubra entrada curta, envio válido, salvamento, sucesso, falha e mudança no store.

Fluxo prático e CTA

Em um repositório real, não comece com “construa a tela inteira”. Peça análise de riscos, faça uma mudança pequena, rode typecheck e testes, depois avance. Diffs pequenos melhoram a revisão.

Para equipes, documente regras em CLAUDE.md: Vue com script setup, estado compartilhado em Pinia setup stores, derivados em computed, efeitos em watch, verificação com npm run typecheck e npm run test:unit. ClaudeCodeLab pode ajudar a transformar isso em processo repetível por meio de treinamento e consultoria Claude Code.

Resumo

Claude Code acelera refatorações Vue 3, formulários, Pinia, composables e testes. A qualidade depende do limite do prompt: arquivos permitidos, estilo Vue, o que não pode mudar e comandos de verificação.

Quem está começando deve trabalhar com um SFC, um composable, um store e um arquivo de teste. Esse ciclo pequeno ensina o modelo mental do Vue e gera código próximo de produção.

Resultado ao testar

Testei esse fluxo em um projeto pequeno com Vue 3 + TypeScript. Pedir formulário, store, composable e testes em uma única solicitação gerou um diff grande e mais demorado de revisar. Separar em SFC, Pinia, composable e Vitest facilitou a inspeção. As restrições mais úteis foram “não mutar props”, “não introduzir any” e “usar computed para valores derivados”. O resultado ficou mais fácil de validar com npm run typecheck e npm run test:unit.

#Claude Code #Vue.js #Nuxt.js #Pinia #Composition API
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.