Capítulo 02: Reatividade Básica: ref(), reactive(), toRefs() e Desestruturação

Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript


🗺️ Mapa Conceitual da Reatividade Vue 3

flowchart TD
    A["Estado Declarado: ref(v) ou reactive(obj)"] --> B["Vue Reactivity Engine (ES6 Proxy)"]
    B --> C["Operação de Leitura: get()"]
    B --> D["Operação de Escrita: set()"]
    C --> E["track(): Registra Efeito Ativo (Dep Map)"]
    D --> F["trigger(): Notifica Assinantes e Dispara Render"]
    E & F --> G["Reconciliação e Atualização Cirúrgica do DOM"]

    subgraph REF_VS_REACTIVE["Diferenciação de Tipos"]
        H["ref<T>: Envelopamento .value (Primitivos e Objetos)"]
        I["reactive<T>: Proxy Profundo (Apenas Objetos)"]
        J["toRefs(obj): Converte chaves em Refs individuais"]
        K["shallowRef<T>: Rastreia apenas nova atribuição"]
    end

    B --> REF_VS_REACTIVE

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style G fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style REF_VS_REACTIVE fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px

🏛️ 1. O Mecanismo Interno do Sistema de Reatividade (ES6 Proxy)

Diferente do Vue 2, que interceptava mutações através de Object.defineProperty (impondo limitações graves como a incapacidade de detectar adição de propriedades ou mutações em índices de arrays), o Vue 3 utiliza Proxies nativos do JavaScript (ES6).

1.1 Rastreamento de Dependências: track() e trigger()

O núcleo da reatividade reativa apoia-se em duas operações determinísticas:

  1. track(target, key): Quando uma propriedade reativa é lida durante a execução de uma função de renderização, de um computed ou de um watchEffect, o Vue registra a função corrente como dependente ativa (Subscriber) associada àquela chave em um WeakMap global.
  2. trigger(target, key): Quando uma propriedade recebe um novo valor, a armadilha set do Proxy localiza todas as dependências cadastradas no WeakMap e as enfileira na fila de microtarefas do navegador para reavaliação.

1.2 ref() vs reactive(): Regras Arquiteturais

  • ref<T>(): Cria um objeto de referência reativo contendo a propriedade .value. Funciona tanto com tipos primitivos (string, number, boolean, symbol) quanto com estruturas complexas. No <template>, o compilador SFC realiza o desempacotamento automático (auto-unwrapping), dispensando a escrita de .value.
  • reactive<T>(): Cria um Proxy direto sobre o objeto alvo. Funciona exclusivamente com tipos de referência (Object, Array, Map, Set). Não possui propriedade .value.

1.3 O Perigo da Perda de Reatividade na Desestruturação

Ao executar a desestruturação padrão do JavaScript em um objeto reactive:

// ❌ ANTI-PATTERN: QUEBRA DE REATIVIDADE
const state = reactive({ count: 0, user: 'Admin' });
const { count, user } = state; // 'count' torna-se uma variável numérica primitiva desacoplada do Proxy!

Para manter a ligação com o Proxy, o desenvolvedor deve utilizar toRefs(state) ou toRef(state, 'count'), que convertem as propriedades em referências do tipo Ref<T> sincronizadas com o objeto original.


⚖️ 2. Tabela Comparativa de Estruturas Reativas

Característica ref(val) reactive(obj) shallowRef(val)
Tipos Suportados Primitivos e Objetos/Arrays. Apenas Objetos e Coleções (Map, Set). Primitivos, Objetos ou Arrays volumosos.
Acesso ao Valor .value no <script>, direto no <template>. Acesso direto às propriedades sem .value. .value obrigatório para atribuição.
Profundidade (Depth) Reatividade Profunda (Deep Reactivity). Reatividade Profunda (Deep Reactivity). Apenas superficial (não reage a mutações internas).
Desestruturação Segura Mantém reatividade direta. Quebra reatividade sem toRefs(). Exige substituição da referência raiz.
Casos de Uso Ideais Estado atômico, formulários, contadores, IDs. Estados agrupados de domínio, modelos de dados. Logs, tabelas com 50.000+ linhas, instâncias de SDKs.

⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)

Anti-Pattern 1: Esquecer .value em Lógica Condicional dentro de <script setup>

Erro Comum: Escrever if (isOpen) ao invés de if (isOpen.value). Como isOpen é um objeto de referência (wrapper), o JavaScript avalia o objeto como truthy mesmo que o valor interno seja false. Abordagem Correta: Adote regras de lint como @typescript-eslint/strict-boolean-expressions para impedir que Ref<boolean> seja avaliado diretamente sem .value.

Anti-Pattern 2: Sobrescrever a Variável do reactive()

Erro Comum:

let userProfile = reactive({ id: 1, name: 'Ana' });
// Tentativa de atualizar após retorno da API:
userProfile = { id: 2, name: 'Carlos' }; // Destrói o Proxy original; o template deixa de atualizar!

Abordagem Correta: Use ref para o objeto raiz (const userProfile = ref(...) e atualize com userProfile.value = novoObjeto) ou utilize Object.assign(userProfile, novoObjeto).


💻 4. Código Conceitual Arquitetural Comentado

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

// 1. Ref simples para controle de flag primitiva
const isLoading = ref<boolean>(false);

// 2. Estado corporativo complexo via reactive
interface ServerClusterConfig {
  datacenter: string;
  maxNodes: number;
  tags: string[];
}

const clusterState = reactive<ServerClusterConfig>({
  datacenter: 'us-central-1',
  maxNodes: 64,
  tags: ['production', 'pci-dss']
});

// 3. Desestruturação com toRefs garantindo reatividade bidirecional contínua
const { datacenter, maxNodes } = toRefs(clusterState);

// 4. shallowRef para grande massa de telemetria sem sobrecarga de Proxy recursivo
const telemetryLogs = shallowRef<string[]>(['10:00:00 - Inicialização do nó 01']);

function pushTelemetryEvent(): void {
  // Em shallowRef, mutações in-place não disparam reatividade por padrão
  telemetryLogs.value.push(`${new Date().toLocaleTimeString()} - Evento de checagem de saúde`);
  // Força atualização explícita do DOM
  triggerRef(telemetryLogs);
}

function scaleCluster(): void {
  // Alteração refletida tanto em maxNodes quanto em clusterState.maxNodes
  maxNodes.value += 8;
}
</script>

<template>
  <div class="max-w-md mx-auto p-6 bg-white border rounded-2xl shadow-sm space-y-4">
    <h3 class="font-bold text-slate-800 text-lg">Cluster: {{ datacenter }}</h3>
    <p class="text-sm text-slate-600">Nós Máximos: <strong class="text-indigo-600">{{ maxNodes }}</strong></p>

    <div class="flex gap-2">
      <button
        @click="scaleCluster"
        class="px-3 py-1.5 text-xs font-semibold bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"
      >
        Expandir Nós (+8)
      </button>
      <button
        @click="pushTelemetryEvent"
        class="px-3 py-1.5 text-xs font-semibold bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200"
      >
        Registrar Telemetria (triggerRef)
      </button>
    </div>

    <div class="bg-slate-900 text-emerald-400 p-3 rounded-xl font-mono text-xs max-h-32 overflow-y-auto">
      <div v-for="(log, idx) in telemetryLogs" :key="idx">{{ log }}</div>
    </div>
  </div>
</template>

🛠️ 5. Cheat-Sheet de Reatividade Vue 3

Função Sintaxe Canônica Comportamento Principal
ref(x) const n = ref(10) Envelopa qualquer valor em { value: 10 }. Desempacotado no template.
reactive(obj) const s = reactive({ a: 1 }) Cria Proxy profundo. Não use em primitivos. Cuidado com desestruturação.
toRefs(obj) const { a } = toRefs(s) Converte cada chave do objeto reativo em uma Ref conectada.
toRef(obj, 'k') const a = toRef(s, 'a') Converte uma propriedade única em uma Ref.
shallowRef(x) const l = shallowRef([...]) Rastreia apenas a reatribuição l.value = [...]. Otimização para listas gigantes.
triggerRef(r) triggerRef(l) Força disparo manual de reatividade para uma shallowRef.