Capítulo 14: Plugins do Pinia, Persistência de Estado e Setup Stores
Capítulo 14: Plugins do Pinia, Persistência de Estado e Setup Stores
Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript
🗺️ Mapa Conceitual da Arquitetura de Plugins do Pinia
flowchart TD
A["Instância Central: createPinia()"] --> B["pinia.use(meuPlugin)"]
subgraph CONTEXTO_PLUGIN["PiniaPluginContext"]
C["store: Instância Reativa da Store ($id)"]
D["options: Opções declaradas na store"]
E["app: Instância da aplicação Vue"]
end
B --> CONTEXTO_PLUGIN
C --> F["1. Hidratação Inicial a partir do LocalStorage"]
C --> G["2. store.$subscribe(): Escuta Mutações e Persiste"]
C --> H["3. window.addEventListener('storage'): Sincronização Multi-Aba"]
F & G & H --> I["Estado Persistente e Resiliente a Reloads (F5)"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style CONTEXTO_PLUGIN fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style I fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Sistema de Plugins e Extensibilidade do Pinia
O Pinia possui uma arquitetura de plugins extremamente elegante baseada em interceptação de contexto. Através de pinia.use(), você pode injetar propriedades globais, adicionar persistência em disco, integrar ferramentas de telemetria/analytics ou orquestrar sincronizações em segundo plano sem alterar uma única linha de código das suas stores de negócio.
1.1 O Objeto PiniaPluginContext
Toda função de plugin recebe como argumento um objeto de contexto com três propriedades fundamentais:
store: A instância individual da store sendo inicializada (contendo$id,$state,$patch,$subscribe).options: As opções configuradas pelo desenvolvedor na chamada dedefineStore(id, fn, options).app: A instância do app Vue corrente.
1.2 Subscrição a Mutações com $subscribe
Para reagir a qualquer modificação de estado sem criar dezenas de watch manuais, utiliza-se store.$subscribe(callback):
- Executa sempre que qualquer propriedade de estado daquela store for modificada.
- Recebe informações detalhadas da mutação (
mutation.type,mutation.storeId) e o snapshot do novo estado. - Aceita a opção
{ detached: true }, garantindo que a subscrição continue ativa mesmo se o componente que a invocou for destruído.
1.3 Mutações em Lote com $patch
Quando for necessário alterar 5 ou 10 variáveis simultaneamente (ex: ao restaurar uma sessão do disco), usar atribuições individuais (store.a = 1; store.b = 2;) dispararia múltiplos ciclos de reconciliação de reatividade. Com store.$patch({ a: 1, b: 2 }), todas as mutações são agrupadas de forma atômica em um único evento.
1.4 Sincronização Multi-Aba via StorageEvent
Em sistemas corporativos, se o usuário fizer logout ou trocar de filial em uma aba do navegador, as outras abas abertas devem refletir essa alteração imediatamente. Escutando o evento nativo window.addEventListener('storage', ...), o plugin do Pinia detecta alterações no localStorage geradas por outras abas e executa $patch() instantaneamente.
⚖️ 2. Tabela Comparativa de Estratégias de Persistência
| Abordagem | Manutenibilidade | Suporte a SSR (Nuxt) | Complexidade |
|---|---|---|---|
| Persistência Manual em Componentes | Baixíssima (lógica espalhada em onMounted). |
Péssima (risco de hidratação incorreta). | Baixa inicialmente, alta com o tempo. |
Plugin Customizado com pinia.use |
Altíssima (centralizado e desacoplado). | Excelente (verificação typeof window). |
Média (escrito uma única vez). |
| Bibliotecas Externas (pinia-plugin-persistedstate) | Alta (pronta para uso corporativo). | Alta (suporta cookies e storage). | Baixa (apenas configuração). |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Tentar Acessar localStorage durante o Build no Servidor (SSR)
Erro Comum: Executar window.localStorage.getItem(...) na raiz do plugin sem validar se o ambiente é o cliente. O servidor Node.js/Nitro travará com ReferenceError: window is not defined.
Abordagem Correta: Encapsule acessos ao disco em if (typeof window !== 'undefined') { ... }.
Anti-Pattern 2: Serializar Objetos Cíclicos ou Funções
Erro Comum: Tentar persistir instâncias de classes complexas que possuem referências circulares ou funções internas via JSON.stringify. O navegador lançará TypeError: Converting circular structure to JSON.
💻 4. Código Conceitual Arquitetural Comentado
// src/plugins/piniaAuditPlugin.ts
import type { PiniaPluginContext } from 'pinia';
// Extensão de tipos TypeScript para autocompletion estrito
declare module 'pinia' {
export interface DefineStoreOptionsBase<S, Store> {
auditTrail?: boolean;
}
}
export function piniaAuditPlugin({ store, options }: PiniaPluginContext): void {
// Executa apenas se a store habilitar a flag 'auditTrail'
if (!options.auditTrail) return;
console.log(`[AuditPlugin] Inicializando trilha de auditoria para store: ${store.$id}`);
// Subscrição a mutações desacoplada do componente
store.$subscribe((mutation, state) => {
const entry = {
timestamp: new Date().toISOString(),
storeId: mutation.storeId,
mutationType: mutation.type,
stateSnapshot: state
};
console.log('[Audit Log Entry]', entry);
// Pode disparar beacon para endpoint de segurança ou datadog
}, { detached: true });
}
🛠️ 5. Cheat-Sheet do Sistema de Plugins do Pinia
// 1. Registro do Plugin na criação da instância
import { createPinia } from 'pinia';
import { meuPluginPersistencia } from '@/plugins/meuPlugin';
const pinia = createPinia();
pinia.use(meuPluginPersistencia);
// 2. Uso do $patch para atualização atômica em lote
store.$patch({
contador: 10,
usuario: { nome: 'Carlos', ativo: true }
});
// 3. Subscrição manual de mutações
const unsubscribe = store.$subscribe((mutation, state) => {
// mutation.type: 'direct' | 'patch object' | 'patch function'
});