Skip to content

Code Style - Padrões de Código

Estrutura de Componentes Vue

OBRIGATÓRIO: Seguir a ordem <template>, <script setup>, <style>

vue
<template>
  <!-- Template HTML -->
</template>

<script setup lang="ts">
// Lógica do componente
</script>

<style lang="scss" scoped>
// Estilos SCSS
</style>

Regras de Código

RegraDescrição
Sem comentáriosNão adicionar comentários no código. O código deve ser autoexplicativo
Script SetupUsar sempre <script setup lang="ts">
TypeScriptTodo código deve ser tipado
Sem tipar retornoNÃO tipar retorno de funções. O TypeScript infere automaticamente
Sem if inlinePROIBIDO if em uma linha. Sempre usar bloco { } com quebra de linha
Sem return inlinePROIBIDO return na mesma linha do if. Sempre dentro do bloco { }
Linha vazia após ifSempre deixar uma linha vazia após o fechamento } de um bloco if
Linha vazia após const/letSempre deixar uma linha vazia após cada declaração const ou let
SCSSUsar <style lang="scss" scoped> para estilos
ComposablesExtrair lógica reutilizável em composables
Sem aninhamentoPROIBIDO aninhar if ou for. Usar early returns e métodos auxiliares
Responsabilidade únicaCada método deve ter apenas uma responsabilidade. Extrair lógica em funções menores
Sem lógica em componentesComponentes são pura view. Toda lógica de negócio, filtros, computeds e ações deve ficar em stores ou composables
Sem try/catch em componentesTratamento de erros deve ficar na store ou composable. Componentes apenas chamam métodos
storeToRefsUsar storeToRefs(store) para expor variáveis reativas. Desestruturar ações diretamente da store
BEM no SCSSUsar convenção BEM (block__element--modifier) com @apply Tailwind nos estilos SCSS
Sem cores hardcodedNão usar cores fixas (rgba, #hex). Usar classes Tailwind com variantes dark: ou variáveis CSS do tema para suportar tema claro/escuro e customização futura
Sem props/emits em nestedComponentes nested dentro do mesmo módulo devem acessar a store diretamente via storeToRefs() em vez de receber dados por props/emits. Props/emits são para componentes genéricos reutilizáveis entre módulos
router.push pelo namePROIBIDO usar router.push('/path') com path string. Sempre usar router.push({ name: 'route-name' }) pois o path pode mudar
Erros do backend no catchNo .catch() de chamadas de serviço, sempre exibir a mensagem vinda do backend: catch((e) => { message.error(e?.response?.data?.message) }). Não usar mensagens genéricas de i18n no catch, o backend já retorna mensagens assertivas

Separação de responsabilidades: componentes vs stores

Componentes são pura view

Componentes não devem conter lógica de negócio. Toda lógica deve estar em stores ou composables.

typescript
// ❌ ERRADO - lógica no componente
const search = ref('')
const debouncedSearch = ref('')
const updateDebounced = debounce((v: string) => { debouncedSearch.value = v }, 500)
watch(search, (v) => updateDebounced(v))
const filtered = computed(() => items.filter(...))
const handleDelete = (id: string) => { dialog.warning({ onPositiveClick: () => store.delete(id) }) }

// ✅ CORRETO - tudo na store, componente só consome
const store = useItemsStore()
const { search, filteredItems, loading } = storeToRefs(store)
const { init, handleDelete } = store

storeToRefs para variáveis, desestruturação para ações

typescript
// ❌ ERRADO - acessar tudo via store.xxx
const store = useStore()
// template: store.loading, store.items, store.deleteItem()

// ✅ CORRETO - separar refs de ações
const store = useStore()
const { items, loading, search } = storeToRefs(store)
const { deleteItem, loadItems, init } = store

Sem try/catch em componentes

typescript
// ❌ ERRADO - try/catch no componente
const handleSave = async () => {
  saving.value = true
  try {
    await api.save(data)
    message.success('Salvo')
  } catch {
    message.error('Erro')
  } finally {
    saving.value = false
  }
}

// ✅ CORRETO - componente só chama a store
const handleSave = async () => {
  await saveProfile()
}

Princípios de Clean Code

Sem if inline e sem return inline

typescript
// ❌ ERRADO - if inline com return
if (!user) return null
if (items.length === 0) return

// ✅ CORRETO - bloco com chaves e quebra de linha
if (!user) {
  return null
}

if (items.length === 0) {
  return
}

Linha vazia após if, const e let

typescript
// ❌ ERRADO - sem espaçamento
const user = getUser()
const name = user.name
if (!name) {
  return
}
const formatted = formatName(name)

// ✅ CORRETO - linha vazia após cada const/let e após cada bloco if
const user = getUser()

const name = user.name

if (!name) {
  return
}

const formatted = formatName(name)

Não tipar retorno de funções

typescript
// ❌ ERRADO - retorno tipado manualmente
const getUser = (): User => { ... }
const isValid = (value: string): boolean => { ... }

// ✅ CORRETO - TypeScript infere o retorno
const getUser = () => { ... }
const isValid = (value: string) => { ... }

PROIBIDO aninhamento de estruturas de controle

typescript
// ❌ ERRADO - if aninhado
const processItems = (items: Item[]) => {
  if (items.length > 0) {
    for (const item of items) {
      if (item.active) {
        // lógica
      }
    }
  }
}

// ✅ CORRETO - early return + método auxiliar
const processItems = (items: Item[]) => {
  if (items.length === 0) {
    return
  }

  const activeItems = items.filter((item) => item.active)

  activeItems.forEach(processItem)
}

const processItem = (item: Item) => {
  // lógica isolada
}

Responsabilidade única por método

typescript
// ❌ ERRADO - múltiplas responsabilidades
const handleSubmit = async () => {
  const isValid = validateForm()
  if (!isValid) {
    showError('Formulário inválido')
    return
  }
  const data = transformData(formData)
  await api.save(data)
  showSuccess('Salvo!')
  router.push('/list')
}

// ✅ CORRETO - responsabilidades separadas
const handleSubmit = async () => {
  if (!validateForm()) {
    return showValidationError()
  }

  await saveData()
  navigateToList()
}

const showValidationError = () => showError('Formulário inválido')

const saveData = async () => {
  const data = transformData(formData)

  await api.save(data)
  showSuccess('Salvo!')
}

const navigateToList = () => router.push('/list')

Nomenclatura

TipoConvençãoExemplo
ComponentesPascalCaseContractList.vue
ComposablescamelCase com prefixo useuseContracts.ts
ServicescamelCase com sufixo ServicegetContractsService
StorescamelCase com prefixo useuseContractsStore
Types/InterfacesPascalCaseContract, ContractPart
Arquivos de páginaPascalCaseListContracts.vue

Padrão de Props e Emits

typescript
const props = defineProps<{
  currentStep: number
  title: string
}>()

const emit = defineEmits<{
  (e: 'update', value: string): void
  (e: 'setStep', index: number): void
}>()

Padrão de Composables

typescript
export const useContracts = () => {
  const items = ref<Contract[]>([])
  const loading = ref(false)

  const loadContracts = async () => {
    loading.value = true
    const { data } = await getContractsService()
    items.value = data
    loading.value = false
  }

  return {
    items,
    loading,
    loadContracts,
  }
}

Regras de i18n (Traduções)

RegraDescrição
Sentence caseUsar letra maiúscula apenas no início da frase e em nomes próprios. Nunca usar Title Case (maiúscula em todas as palavras)
Nomes própriosManter maiúsculas apenas em: siglas (CPF, CNPJ, FAQ), marcas (WhatsApp), títulos de documentos legais (Termos de Serviço, Política de Privacidade), categorias jurídicas (Pessoa Jurídica, Pessoa Física)

Sentence case em traduções

❌ ERRADO - Title Case
"Novo Contrato"
"Total de Horas"
"Relatório Mensal"
"Histórico de Contratos"

✅ CORRETO - Sentence case
"Novo contrato"
"Total de horas"
"Relatório mensal"
"Histórico de contratos"

✅ CORRETO - Exceções (nomes próprios, siglas, títulos legais)
"Termos de Serviço"
"Política de Privacidade"
"Pessoa Jurídica"
"CPF do responsável"

Checklist para Novos Componentes

  • [ ] Usar <script setup lang="ts">
  • [ ] Não adicionar comentários no código
  • [ ] Seguir ordem: template → script → style
  • [ ] Usar <style lang="scss" scoped>
  • [ ] Tipar todas as props e emits
  • [ ] Usar composables para lógica reutilizável
  • [ ] Seguir convenções de nomenclatura
  • [ ] Usar classes Tailwind para layout básico
  • [ ] Usar i18n para textos estáticos