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


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

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

6. Persistência da Pinia

(1) Instale o plug-in

BASH
npm install pinia-plugin-persistedstate

(2) Configuração do main.js

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

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. Exemplo completo: 5 principais recursos do backend de comércio eletrônico da Pinia Store

▶ Exemplo: 1. 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

▶ Exemplo: 2. 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

▶ Exemplo: 3. 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

▶ Exemplo: 4. 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

▶ 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 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%