Especialização em Front-End com Vue • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 02 — Gestão Segura de Reatividade com ref(), reactive() e toRefs()

O exemplo a seguir ilustra a diferença prática entre ref e reactive, o problema da perda de reatividade na desestruturação de objetos reativos e como resolvê-lo com toRefs() e toRef().

<!-- src/components/ReactivityLab.vue -->
<script setup lang="ts">
import { ref, reactive, toRefs, shallowRef } from 'vue';

// 1. Ref simples para valores primitivos ou objetos atômicos
const counter = ref<number>(0);

// 2. Objeto complexo gerenciado por Proxy via reactive()
interface UserSession {
  username: string;
  role: 'admin' | 'editor' | 'viewer';
  loginCount: number;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

const session = reactive<UserSession>({
  username: 'carlos.silva',
  role: 'editor',
  loginCount: 14,
  preferences: {
    theme: 'dark',
    notifications: true
  }
});

// 3. Desestruturação SEGURA: preserva reatividade convertendo propriedades em Refs
const { username, role, loginCount } = toRefs(session);

// 4. shallowRef para payloads gigantescos que não precisam de deep reactivity
const heavyDataset = shallowRef<number[]>([10, 20, 30, 40]);

function incrementCounter(): void {
  counter.value++;
}

function updateSession(): void {
  // Alteração direta reflete nas variáveis desestruturadas
  session.loginCount++;
  session.preferences.theme = session.preferences.theme === 'dark' ? 'light' : 'dark';
}

function promoteToAdmin(): void {
  // Alterando diretamente a ref extraída via toRefs
  role.value = 'admin';
}

function replaceHeavyData(): void {
  // shallowRef só reage à atribuição de uma nova referência
  heavyDataset.value = [...heavyDataset.value, Math.floor(Math.random() * 100)];
}
</script>

<template>
  <div class="p-6 max-w-xl mx-auto bg-slate-50 border rounded-2xl shadow-sm space-y-6">
    <h2 class="text-xl font-bold text-slate-800 border-b pb-2">
      🧪 Laboratório de Reatividade Vue 3
    </h2>

    <!-- Seção 1: Ref Simples -->
    <div class="bg-white p-4 rounded-xl border">
      <h3 class="font-semibold text-slate-700">1. Ref Primitivo</h3>
      <p class="text-sm text-slate-500 mt-1">Valor: <strong class="text-indigo-600">{{ counter }}</strong></p>
      <button
        @click="incrementCounter"
        class="mt-2 px-3 py-1.5 text-xs font-semibold bg-indigo-50 text-indigo-700 rounded-md hover:bg-indigo-100"
      >
        Incrementar Ref (+1)
      </button>
    </div>

    <!-- Seção 2: Reactive & toRefs -->
    <div class="bg-white p-4 rounded-xl border space-y-2">
      <h3 class="font-semibold text-slate-700">2. Estado Reativo com toRefs()</h3>
      <div class="text-sm text-slate-600 space-y-1">
        <p>Usuário: <code class="font-mono bg-slate-100 px-1 py-0.5 rounded">{{ username }}</code></p>
        <p>Perfil: <span class="font-bold text-amber-600 uppercase">{{ role }}</span></p>
        <p>Acessos: <strong>{{ loginCount }}</strong></p>
        <p>Tema: <span class="capitalize">{{ session.preferences.theme }}</span></p>
      </div>

      <div class="flex gap-2 pt-2">
        <button
          @click="updateSession"
          class="px-3 py-1.5 text-xs font-semibold bg-emerald-50 text-emerald-700 rounded-md hover:bg-emerald-100"
        >
          Registrar Acesso e Alternar Tema
        </button>
        <button
          @click="promoteToAdmin"
          class="px-3 py-1.5 text-xs font-semibold bg-amber-50 text-amber-700 rounded-md hover:bg-amber-100"
        >
          Promover para Admin
        </button>
      </div>
    </div>

    <!-- Seção 3: shallowRef -->
    <div class="bg-white p-4 rounded-xl border">
      <h3 class="font-semibold text-slate-700">3. shallowRef (Desempenho)</h3>
      <p class="text-sm text-slate-500 mt-1">Total de Itens: <strong>{{ heavyDataset.length }}</strong></p>
      <p class="text-xs font-mono text-slate-400 truncate mt-1">Valores: {{ heavyDataset.join(', ') }}</p>
      <button
        @click="replaceHeavyData"
        class="mt-2 px-3 py-1.5 text-xs font-semibold bg-slate-100 text-slate-700 rounded-md hover:bg-slate-200"
      >
        Substituir Referência do Array
      </button>
    </div>
  </div>
</template>

🖥️ Saída / Comportamento Esperado

Renderização Inicial:
Counter: 0
Usuário: carlos.silva | Perfil: EDITOR | Acessos: 14 | Tema: dark
Itens do Array shallowRef: 4 itens [10, 20, 30, 40]

Ações do Usuário:
1. Clicar em "Incrementar Ref":
   Counter atualiza de 0 para 1 instantaneamente.
2. Clicar em "Registrar Acesso e Alternar Tema":
   loginCount passa para 15 (sincronizado tanto em session.loginCount quanto na ref desestruturada loginCount).
   Tema alterna de 'dark' para 'light'.
3. Clicar em "Promover para Admin":
   Role passa de 'editor' para 'admin' reativamente.
4. Clicar em "Substituir Referência do Array":
   Array expande para 5 elementos, disparando renderização de heavyDataset.length.

🔍 Destaques da Implementação

  1. Evitando Quebra de Reatividade: A instrução const { username, role, loginCount } = toRefs(session) cria refs ligadas ao objeto original sem copiar valores estáticos primitivos.
  2. Proxy do ES6: O reactive() intercepta operações get/set de propriedades aninhadas, ao contrário do Object.defineProperty do Vue 2 que não detectava adição/deleção de chaves.
  3. shallowRef para Grande Volume: Evita a conversão recursiva de arrays de 10.000+ itens em proxies, economizando memória e ciclos de CPU.