Vue.js: Gerenciamento de Estado com Pinia

Última atualização: 2026-10-20

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.

Essencial relacionados:computed()

1. O que você vai aprender


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:

JS
// ❌ 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

JS
// 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)
    }
  }
})
VUE
<!-- 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>
VUE
<!-- 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:


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)

JS
// 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)

JS
// 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

▶ Exemplo: Estilo “Opções” x Estilo “Configuração”

JS
// 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 }
})
▶ Experimente

Saída:

TEXT
As duas stores começam com count = 0 e double = 0.
increment() → count 1 e double 2 nas duas versões.
No estilo Options o estado vem de state(), nos getters usa-se s.count.

5. As 5 APIs principais

(1) estado: dados

JS
// 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

JS
// 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

JS
// 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

VUE
<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

JS
// ⚠️ 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

▶ Exemplo: 5 APIs principais

JS
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 }
})
▶ Experimente

Saída:

TEXT
useStore().count vale 0 e double vale 0.
increment() → count 1 e double 2 em qualquer componente.
storeToRefs entrega count como ref: ao desestruturar, o template continua reagindo.

▶ Exemplo: 5 maneiras de acessar o Pinia

VUE
<!-- 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>
▶ Experimente

Saída:

TEXT
Acesso direto: o template mostra o tamanho atual do carrinho.
Com storeToRefs, items e total continuam reagindo após a desestruturação.
Ao adicionar um item, o watch loga Cart updated: 3.

6. Persistência da Pinia

(1) Instale o plug-in

BASH
npm install pinia-plugin-persistedstate

(2) Configuração do main.js

(3) 5 maneiras de persistir a configuração

JS
// 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. Exercício abrangente: persistência da store com 5 configurações

▶ Exemplo: Como persistir 5 tipos de configuração

JS
// 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
  }
}
▶ Experimente

Saída:

TEXT
persist: true grava o estado na chave padrão da store.
Com paths: ['items'] apenas o carrinho é salvo; o resto volta ao valor inicial.
Com storage: sessionStorage os dados somem ao fechar a aba; o serializer troca o formato gravado.

(1) 📊 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

(2) 📊 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

Essencial relacionados:computed()

❓ 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 useMouse e useFetch). 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 desestruturar state; 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


📝 Exercícios

  1. 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
  2. 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)
  3. Problema de desafio (Dificuldade: ⭐⭐⭐)

    Implementar um sistema completo de “loja” de comércio eletrônico (back-end):

    1. 5 lojas: autenticação / carrinho / tema / notificação / produto
    2. Cada Store: estado completo + getters + ações
    3. Persistência (auth/cart/theme)
    4. Configuração do Style + TypeScript
    5. Desconstruindo o storeToRefs
    6. Chamadas entre páginas (o carrinho acessa o módulo de autenticação para verificar se o usuário está logado)
    7. Testes unitários (usando o Vitest)
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%