Appearance
State Management: Pinia em hooks/
Esta doc complementa
code-style.md. Em caso de conflito,code-style.mdvence.
Onde stores moram
Todos os stores Pinia ficam em hooks/useXxx.ts dentro do módulo. Não existem as pastas stores/ nem composables/ por módulo, elas foram unificadas em hooks/.
src/modules/<mod>/
├── services/
├── hooks/ ← stores Pinia + composables, juntos
├── components/
├── pages/
└── router/Stores globais (sem dono de módulo) ficam em src/hooks/.
Nome do hook: sem sufixo Store
typescript
export const useContracts = defineStore('contracts', () => { ... })
export const useCompliance = defineStore('compliance', () => { ... })Proibido useContractsStore, useComplianceStore. O id interno do defineStore é livre; o export é sempre useXxx.
Estrutura de uma store
typescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getContractsService } from '@/modules/contracts/services/contracts'
import type { Contract, ContractFilter } from '@/domain/contract'
export const useContracts = defineStore('contracts', () => {
const items = ref<Contract[]>([])
const total = ref(0)
const loading = ref(false)
const search = ref('')
const activeItems = computed(() => items.value.filter((i) => i.status === 'active'))
const loadItems = (params?: ContractFilter) => {
loading.value = true
return getContractsService(params)
.then((response) => {
items.value = response.data.data
total.value = response.data.total
})
.finally(() => {
loading.value = false
})
}
const setSearch = (value: string) => {
search.value = value
return loadItems({ search: value })
}
return { items, total, loading, search, activeItems, loadItems, setSearch }
})Sem try/catch: erros sobem naturalmente; .catch() no consumer mostra e?.response?.data?.message. Use .finally() para liberar loading.
Consumo: storeToRefs + destructuring
typescript
import { storeToRefs } from 'pinia'
import { useContracts } from '@/modules/contracts/hooks/useContracts'
const contracts = useContracts()
const { items, loading, activeItems } = storeToRefs(contracts)
const { loadItems, setSearch } = contracts
loadItems()contracts (a variável intermediária) só existe para destruturar. Nunca referencie contracts.x depois disso.
Proibido:
const store = useContracts()seguido destore.itemsem template/script, perde reatividade e quebra a regra do split.- Wrappar a store em
reactive({ ... }): causa dupla indireção e quebrastoreToRefs.
Hook complexo: inverter dependências
Quando o defineStore passa de ~100 linhas ou cobre múltiplas responsabilidades, separe em hooks menores em vez de esconder a complexidade em helpers privados. Hooks menores podem importar uns aos outros (sem ciclos), e o componente consome todos.
Padrão de referência: src/modules/contracts/components/Create/Flow/Review/ContactReview.vue.
Tipos não vivem no arquivo do hook
Nada de type Foo = ... em useXxx.ts. Tipos vão para src/domain/<mod>/types.ts (entidades) ou src/modules/<mod>/models/ (UI). Helpers vão para src/modules/<mod>/helpers/<topic>.ts (kebab-case) ou src/domain/<mod>/.
Padrão para listas paginadas
Reusar usePaginatedList em src/composables/usePaginatedList.ts: não duplicar paginação em cada store.