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:

  1. store: A instância individual da store sendo inicializada (contendo $id, $state, $patch, $subscribe).
  2. options: As opções configuradas pelo desenvolvedor na chamada de defineStore(id, fn, options).
  3. 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'
});