Vue.js: Gerenciamento de Estado com Pinia
Última atualização: 2026-08-26
Pinia é a biblioteca de gerenciamento de estado oficial recomendada pelo Vue 3 — substituindo o Vuex. A Pinia oferece uma API mais concisa, suporte completo ao TypeScript, recarregamento a quente modular e suporte nativo ao DevTools. A equipe do Vue tornou a Pinia a recomendação padrão (o Vuex não é mais mantido).
O Pinia permite gerenciar o estado compartilhado entre componentes (informações do usuário, carrinho de compras, configurações globais) de maneira mais estruturada do que o provide/inject e é 50% mais conciso que o Vuex.
1. O que você vai aprender
- Por que o Pinia é uma alternativa melhor ao Vuex (5 vantagens principais)
- defineStore: Dois estilos (Opções / Configuração)
- estado / getter / ação: três conceitos fundamentais
- Pinia Modular (Multilojas)
- Persistência (pinia-plugin-persistedstate)
- Integração com o DevTools
- 5 cenários-chave do mundo real
2. O pesadelo das “5 inconsistências em um ‘componente’ de carrinho de compras”
(1) Problema: Cinco componentes gerenciam, cada um, seus próprios dados do carrinho de compras
O site de comércio eletrônico da Alice tinha cinco componentes que precisavam de dados do carrinho de compras:
// ❌ The "Broken" Version:5 component 5 set of data
// CartIcon.vue
const cartCount = ref(0)
// ProductCard.vue
const localCart = ref([])
// CartPage.vue
const cart = ref({ items: [], total: 0 })
// CheckoutPage.vue
const myCart = ref([])
// Header.vue
const cartItems = ref([])
O gerente de produto Charlie:
“Alice, quando adiciono um produto no ProductCard, o ícone do carrinho não é atualizado! Vejo ‘0’, mas a página mostra ‘1 item’. Cinco componentes, cinco carrinhos — eles não sincronizam!”
(2) Solução Vue Pinia: 1 store compartilhado por 5 componentes
// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
total: 0
}),
getters: {
itemCount: (state) => state.items.length,
totalPrice: (state) => state.items.reduce((sum, i) => sum + i.price, 0)
},
actions: {
addItem(product) {
this.items.push(product)
this.total += product.price
},
removeItem(id) {
this.items = this.items.filter(i => i.id !== id)
}
}
})
<!-- CartIcon.vue -->
<script setup>
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
// Automatic Response:cart.itemCount It's changed,icon Update Now
</script>
<template>
<span>🛒 {{ cart.itemCount }}</span>
</template>
<!-- ProductCard.vue -->
<script setup>
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
</script>
<template>
<button @click="cart.addItem(product)">Add to Cart</button>
</template>
1 loja, 5 componentes sincronizados em tempo real.
(3) Receita
Depois de usar o Pinia:
- Consistência dos dados: 1 loja, 5 componentes sincronizados em tempo real
- Volume de código: 5 referências → 1 armazenamento (-80%)
- DevTools: Depuração com “Time Travel” (Visualizar o estado em cada etapa)
- TypeScript: Inferência de tipos completa
- Persistência: Salvo automaticamente no localStorage
3. 5 principais vantagens do Pinia em comparação com o Vuex
| Dimensão | Vuex 4 (Vue 3) | Pinia (Vue 3) |
|---|---|---|
| Simplicidade da API | ⭐⭐⭐ Complexa (mutações / ações) | ⭐⭐⭐⭐⭐ Simples (estado / getters / ações) |
| TypeScript | ⭐⭐⭐ Requer configuração adicional | ⭐⭐⭐⭐⭐ Suporte nativo |
| API de composição | ⭐⭐ Não é intuitiva | ⭐⭐⭐⭐⭐ Componente de primeira classe |
| DevTools | ⭐⭐⭐⭐ | Melhorias ⭐⭐⭐⭐⭐ |
| Tamanho do pacote | ~10 KB | ~1 KB |
O Vue recomenda oficialmente o Pinia, e o Vuex 4 entrou em modo de manutenção (não serão adicionados novos recursos).
4. Dois estilos de defineStore
(1) Estilo de opções (semelhante ao Vuex)
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// state:Data
state: () => ({
count: 0,
name: 'Counter'
}),
// getters:Derived values(Similar computed)
getters: {
doubleCount: (state) => state.count * 2,
isZero: (state) => state.count === 0
},
// actions:Methods(Similar methods)
actions: {
increment() {
this.count++
},
async fetchData() {
const res = await fetch('/api/count')
this.count = await res.json()
}
}
})
(2) Estilo de configuração (recomendado, API composta)
// stores/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
// 1. state (use ref)
const user = ref(null)
const token = ref(localStorage.getItem('token') || '')
// 2. getters (use computed)
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => user.value?.name || 'Guest')
// 3. actions(Ordinary Functions)
function login(credentials) {
// API call...
user.value = { name: 'Alice' }
token.value = 'xxx'
}
function logout() {
user.value = null
token.value = ''
}
return { user, token, isLoggedIn, userName, login, logout }
})
(3) Comparação entre opções e configuração
| Dimensão | Estilo das opções | Estilo de configuração |
|---|---|---|
| Como escrever | state: () => ({}) |
const x = ref() |
| getters | (state) => ... |
computed() |
| ações | function() { this.x } |
Função regular |
| TypeScript | Manual | Automático |
| Combinabilidade | Fraca | Forte (pode ser usado com outros elementos combináveis) |
| Nível de recomendação | Para quem já conhece o Vuex | Recomendado para novos projetos |
5. As 5 APIs principais
(1) estado: dados
// Options Style
state: () => ({
count: 0,
user: null,
items: []
})
// Setup Style
const count = ref(0)
const user = ref(null)
const items = ref([])
(2) getters: valores derivados
// Options Style
getters: {
// Simple getter
doubleCount: (state) => state.count * 2,
// Visit Other getters (use this)
ratio(state) {
return this.doubleCount / 100
},
// Return Function(Parameterization getter)
getItemById: (state) => (id) => {
return state.items.find(i => i.id === id)
}
}
// Setup Style
const doubleCount = computed(() => count.value * 2)
const getItemById = (id) => items.value.find(i => i.id === id)
(3) ações: Métodos
// Options Style
actions: {
// Synchronize
increment() {
this.count++
},
// Asynchronous
async fetchData() {
const res = await fetch('/api/data')
this.data = await res.json()
},
// Visit Others actions
async loginAndFetch(credentials) {
await this.login(credentials)
await this.fetchUser()
}
}
// Setup Style
function increment() {
count.value++
}
async function fetchData() {
const res = await fetch('/api/data')
data.value = await res.json()
}
(4) Uso em componentes
<script setup>
import { useCartStore } from '@/stores/cart'
import { storeToRefs } from 'pinia'
const cart = useCartStore()
// 1. Direct Access state(Responsive)
console.log(cart.items)
// 2. Use storeToRefs Destructuring (Keep Reactive)
const { items, total } = storeToRefs(cart)
// 3. Call action
cart.addItem(product)
// 4. Monitoring state Changes
watch(() => cart.items, (newItems) => {
console.log('Cart changed:', newItems)
})
</script>
(5) 5 pontos importantes a serem lembrados
// ⚠️ Note 1:Deconstruction state Responsive Design Missing
const { items } = cart // ❌ items It is a normal value
const { items } = storeToRefs(cart) // ✅ items is ref
// ⚠️ Note 2:Edit state Must use action
cart.items.push(...) // ❌ Not recommended(Edit directly)
cart.addItem(...) // ✅ Use action
// ⚠️ Note 3:action inside this Orientation store
actions: {
increment() {
this.count++ // ✅ this = store
}
}
// ⚠️ Note 4: Getter Cache
getters: {
doubleCount() {
console.log('recomputed') // Print only when dependencies change
return this.count * 2
}
}
// ⚠️ Note 5: useStore in setup, use pinia instance externally
import { getActivePinia } from 'pinia'
const cart = useCartStore(getActivePinia()) // In .js files
6. Persistência da Pinia
(1) Instale o plug-in
npm install pinia-plugin-persistedstate
(2) Configuração do main.js
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.use(pinia)
app.mount('#app')
(3) 5 maneiras de persistir a configuração
// stores/cart.js
export const useCartStore = defineStore('cart', () => {
const items = ref([])
return { items }
}, {
// 1. Default: localStorage key is 'cart'
persist: true,
// 2. Custom key
persist: {
key: 'my-cart',
storage: localStorage
},
// 3. Persist only a portion state
persist: {
paths: ['items'] // Save only items,Do not save total
},
// 4. sessionStorage(Closing the browser clears it)
persist: {
storage: sessionStorage
},
// 5. Custom Serialization(Encryption, etc.)
persist: {
serializer: {
serialize: (value) => btoa(JSON.stringify(value)),
deserialize: (value) => JSON.parse(atob(value))
}
}
})
7. Exemplo completo: 5 principais recursos do backend de comércio eletrônico da Pinia Store
▶ Exemplo: 1. 5 APIs principais
import { defineStore, storeToRefs } from 'pinia'
import { ref, computed } from 'vue'
// 1. state
const count = ref(0)
// 2. getter
const double = computed(() => count.value * 2)
// 3. action
function increment() { count.value++ }
// 4. Export
export const useStore = defineStore('store', () => {
return { count, double, increment }
})
▶ Exemplo: 2. 5 maneiras de acessar o Pinia
<!-- Direct Access -->
<template>{{ cart.items.length }}</template>
<script setup>
const cart = useCartStore()
</script>
<!-- Deconstruction(Stay Responsive)-->
<script setup>
const cart = useCartStore()
const { items, total } = storeToRefs(cart)
</script>
<!-- Monitor Changes -->
<script setup>
watch(() => cart.items, (newItems) => {
console.log('Cart updated:', newItems.length)
})
</script>
▶ Exemplo: 3. Estilo “Opções” x Estilo “Configuração”
// Options Style(Vuex Habits)
export const useStore1 = defineStore('store1', {
state: () => ({ count: 0 }),
getters: { double: (s) => s.count * 2 },
actions: { increment() { this.count++ } }
})
// Setup Style(Recommendations)
export const useStore2 = defineStore('store2', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, double, increment }
})
▶ Exemplo: 4. Como persistir 5 tipos de configuração
// 1. Default
persist: true
// 2. Custom key
persist: { key: 'my-cart' }
// 3. Selectivity
persist: { paths: ['items'] }
// 4. sessionStorage
persist: { storage: sessionStorage }
// 5. Encryption
persist: {
serializer: {
serialize: JSON.stringify,
deserialize: JSON.parse
}
}
▶ Exemplo: 5. Referência rápida para 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| Analisando as respostas “State Lost” | Os dados permanecem inalterados | Use storeToRefs |
| Modificar o estado diretamente | Aviso | Use uma ação em vez disso |
Ação assíncrona sem await |
Resultados desiguais | await cart.fetchData() |
| Pinia Não registrado | useStore is not a function |
main.js app.use(pinia) |
| Campos não salvos após a persistência | Chave incorreta | Verifique a configuração dos caminhos |
▶ Exemplo: 6. 5 Cenários práticos
| Cenário | Loja | Campos-chave |
|---|---|---|
| Auth | useAuthStore | usuário, token, isLoggedIn |
| Carrinho de compras | useCartStore | itens, total, número de itens |
| Tema | useThemeStore | tema, localização, densidade |
| Notificações | useNotificationStore | notificações, não lidas |
| Dados | useDataStore | lista, carregando, erro |
❓ Perguntas Frequentes
P: O que devo escolher, Pinia ou Vuex? R: Pinia. A equipe do Vue recomenda oficialmente o Pinia, e o Vuex 4 não recebe mais manutenção. Novos projetos devem usar o Pinia, e os projetos existentes que utilizam o Vuex podem ser migrados gradualmente.
P: O Pinia substitui o provide/inject? R: Não, não substitui. O Pinia serve para o gerenciamento de estado global, enquanto o provide/inject é destinado à comunicação entre níveis. O Pinia é usado para o “estado em toda a aplicação” (usuário/carrinho), enquanto o provide/inject é usado para “temas/i18n/configuração”.
P: Quando se deve usar o Pinia em vez do Composable? R: O Pinia é usado para o estado global compartilhado entre componentes (como o usuário e o carrinho de compras). O Composable é usado para lógica reutilizável no nível do componente (como
useMouseeuseFetch). O Pinia é mais estruturado e inclui o DevTools.
P: Como o Pinia é modularizado? R: Cada store está em seu próprio arquivo, localizado no diretório
src/stores/. O Pinia oferece suporte automático ao tree-shaking.
P: Quando se deve usar
storeToRefs? R: É necessário usá-lo ao desestruturarstate; caso contrário, perde-se a reatividade. Não é necessário para getters e ações.
P: Quais opções de armazenamento o Pinia suporta para persistência? R: Por padrão, o localStorage; ele também suporta o sessionStorage, armazenamento personalizado (IndexedDB, cookies etc.) e serialização personalizada (criptografada).
P: Qual é o desempenho do Pinia? R: As mudanças de estado acionam atualizações reativas, os getters são armazenados em cache automaticamente e o DevTools oferece otimizações. Ele é 30–50% mais rápido que o Vuex (não utiliza o conceito de mutações).
P: O Pinia pode ser usado com o SSR? R: Sim.
import { createPinia } from 'pinia', é criada uma instância separada para cada solicitação. O Nuxt 3 inclui integração nativa com o Pinia.
📖 Resumo
- A Pinia é a biblioteca de gerenciamento de estado oficialmente recomendada para o Vue 3, substituindo o Vuex
- 5 vantagens principais: Simplicidade / TypeScript / Composição / DevTools / Tamanho reduzido do pacote
- defineStore: Dois estilos: Opções (convenção do Vuex) / Configuração (recomendado)
- 5 APIs principais: state / getters / actions / storeToRefs / Persistence
- Modularidade: um arquivo por loja
- Persistência: pinia-plugin-persistedstate
- Pinia x Composable: Estado global x Lógica de componentes
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Implemente um contador simples:
- estado da contagem
- getter doubleCount
- ações de incremento / decremento / reinicialização
- Distribuído por 3 componentes
-
Problemas avançados (Dificuldade: ⭐⭐)
Implementação do carrinho de compras (loja):
- matriz de itens
- getters itemCount / totalPrice
- ações addItem / removeItem / clearCart
- Salvar no localStorage
- Testado em 5 componentes (ProductCard / CartIcon / CartPage / Checkout / Header)
-
Problema de desafio (Dificuldade: ⭐⭐⭐)
Implementar um sistema completo de “loja” de comércio eletrônico (back-end):
- 5 lojas: autenticação / carrinho / tema / notificação / produto
- Cada Store: estado completo + getters + ações
- Persistência (auth/cart/theme)
- Configuração do Style + TypeScript
- Desconstruindo o
storeToRefs - Chamadas entre páginas (o carrinho acessa o módulo de autenticação para verificar se o usuário está logado)
- Testes unitários (usando o Vitest)