Boas Práticas de Proxy e Reflect Avançados: Construindo Frameworks Reativos do Zero para Times Ágeis Já leu

Entendendo Proxy e Reflect na Prática Proxy e Reflect são dois APIs JavaScript que trabalham juntos para interceptar e personalizar operações em objetos. Um Proxy funciona como intermediário: você define "armadilhas" (traps) que interceptam ações como leitura, escrita e exclusão de propriedades. Reflect é seu complemento, fornecendo métodos que replicam o comportamento padrão do JavaScript. Juntos, eles formam a base para frameworks reativos como Vue.js e MobX. A diferença fundamental: Proxy intercepta operações, enquanto Reflect as executa de forma controlada. Quando você quer criar reatividade, precisa saber exatamente quando uma propriedade é acessada ou modificada. Isso permite rastrear dependências e disparar atualizações automáticas em sua interface. Acessando: ${prop} Alterando ${prop} para ${valor} Construindo um Sistema Reativo Básico Um framework reativo detecta mudanças e propaga atualizações automaticamente. O segredo está em manter um registro de quais propriedades foram acessadas durante a execução de uma função e executar essa função novamente quando essas propriedades mudam. Vamos construir um sistema minimal de reatividade:

Entendendo Proxy e Reflect na Prática

Proxy e Reflect são dois APIs JavaScript que trabalham juntos para interceptar e personalizar operações em objetos. Um Proxy funciona como intermediário: você define "armadilhas" (traps) que interceptam ações como leitura, escrita e exclusão de propriedades. Reflect é seu complemento, fornecendo métodos que replicam o comportamento padrão do JavaScript. Juntos, eles formam a base para frameworks reativos como Vue.js e MobX.

A diferença fundamental: Proxy intercepta operações, enquanto Reflect as executa de forma controlada. Quando você quer criar reatividade, precisa saber exatamente quando uma propriedade é acessada ou modificada. Isso permite rastrear dependências e disparar atualizações automáticas em sua interface.

// Exemplo básico: rastreador de acesso
const target = { nome: 'João', idade: 30 };
const handler = {
  get(target, prop) {
    console.log(`Acessando: ${prop}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, valor) {
    console.log(`Alterando ${prop} para ${valor}`);
    return Reflect.set(target, prop, valor);
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.nome); // "Acessando: nome" → "João"
proxy.idade = 31; // "Alterando idade para 31"

Construindo um Sistema Reativo Básico

Um framework reativo detecta mudanças e propaga atualizações automaticamente. O segredo está em manter um registro de quais propriedades foram acessadas durante a execução de uma função e executar essa função novamente quando essas propriedades mudam.

Vamos construir um sistema minimal de reatividade:

class Reactive {
  constructor(data) {
    this.data = data;
    this.watchers = new Map(); // Prop → Set de callbacks
    this.currentEffect = null;

    return this.createProxy(data);
  }

  createProxy(target) {
    return new Proxy(target, {
      get: (obj, prop) => {
        // Rastrear dependência durante execução de efeito
        if (this.currentEffect) {
          if (!this.watchers.has(prop)) {
            this.watchers.set(prop, new Set());
          }
          this.watchers.get(prop).add(this.currentEffect);
        }
        return Reflect.get(obj, prop);
      },

      set: (obj, prop, valor) => {
        const resultado = Reflect.set(obj, prop, valor);
        // Disparar callbacks quando propriedade muda
        if (this.watchers.has(prop)) {
          this.watchers.get(prop).forEach(callback => callback());
        }
        return resultado;
      }
    });
  }

  effect(fn) {
    this.currentEffect = fn;
    fn();
    this.currentEffect = null;
  }
}

// Uso prático
const state = new Reactive({ contador: 0, nome: 'App' });

state.effect(() => {
  console.log(`Contador: ${state.contador}`);
});

state.contador++; // Executa novamente: "Contador: 1"
state.contador++; // Executa novamente: "Contador: 2"
state.nome = 'Novo'; // Não executa (dependência não registrada)

Este é o padrão fundamental: rastrear leitura durante efeitos e executar novamente em mudanças. Vue 3 e MobX usam variações mais sofisticadas, mas o conceito é idêntico.

Validação e Transformação com Handlers Avançados

Além de get e set, Proxy oferece 13 traps diferentes. Para frameworks robustos, você precisa controlar deleteProperty, has, ownKeys e defineProperty. Vamos criar um sistema com validação:

function createModel(schema) {
  return new Proxy({}, {
    get(target, prop) {
      return Reflect.get(target, prop);
    },

    set(target, prop, valor) {
      const regra = schema[prop];

      if (!regra) {
        throw new Error(`Propriedade inválida: ${prop}`);
      }

      if (regra.type && typeof valor !== regra.type) {
        throw new TypeError(
          `${prop} deve ser ${regra.type}, recebeu ${typeof valor}`
        );
      }

      if (regra.validate && !regra.validate(valor)) {
        throw new Error(`Validação falhou para ${prop}`);
      }

      return Reflect.set(target, prop, valor);
    },

    deleteProperty(target, prop) {
      if (schema[prop]?.required) {
        throw new Error(`Não pode deletar propriedade obrigatória: ${prop}`);
      }
      return Reflect.deleteProperty(target, prop);
    }
  });
}

// Exemplo de uso
const user = createModel({
  email: {
    type: 'string',
    required: true,
    validate: (v) => v.includes('@')
  },
  idade: {
    type: 'number',
    validate: (v) => v >= 18
  }
});

user.email = 'test@example.com'; // OK
user.idade = 25; // OK
user.idade = 15; // Erro: Validação falhou
delete user.email; // Erro: Não pode deletar propriedade obrigatória

Esta abordagem permite criar camadas de validação e segurança que aplicam regras de negócio automaticamente, essencial em aplicações complexas.

Performance e Padrões Avançados

Proxies têm custo de performance. Em frameworks reativos, você não cria um Proxy para cada propriedade — cria um por objeto raiz. Para estruturas aninhadas, você precisa decidir: fazer Proxies profundos (recursivos) ou raso (shallow).

class ReactiveDeep {
  constructor(data) {
    this.watchers = new Map();
    this.currentEffect = null;
    return this.wrap(data);
  }

  wrap(target) {
    if (typeof target !== 'object' || target === null) {
      return target;
    }

    return new Proxy(target, {
      get: (obj, prop) => {
        if (this.currentEffect) {
          const key = `${prop}`;
          if (!this.watchers.has(key)) {
            this.watchers.set(key, new Set());
          }
          this.watchers.get(key).add(this.currentEffect);
        }

        const valor = Reflect.get(obj, prop);
        // Envolver objetos aninhados também
        return typeof valor === 'object' && valor !== null 
          ? this.wrap(valor) 
          : valor;
      },

      set: (obj, prop, valor) => {
        const resultado = Reflect.set(obj, prop, valor);
        if (this.watchers.has(prop)) {
          this.watchers.get(prop).forEach(cb => cb());
        }
        return resultado;
      }
    });
  }

  effect(fn) {
    this.currentEffect = fn;
    fn();
    this.currentEffect = null;
  }
}

const app = new ReactiveDeep({
  user: { perfil: { nome: 'Ana' } }
});

app.effect(() => {
  console.log(app.user.perfil.nome);
});

app.user.perfil.nome = 'Bruno'; // Dispara efeito

Dica de produção: Para aplicações muito grandes, considere lazy-wrapping e memoização de Proxies para evitar recriá-los constantemente.

Conclusão

Dominando Proxy e Reflect, você consegue: (1) Interceptar todas operações em objetos e implementar reatividade automática, fundação de frameworks modernos; (2) Aplicar validação, transformação e segurança em tempo real sem código repetitivo; (3) Entender como Vue.js, MobX e bibliotecas similares funcionam internamente, permitindo debug e otimização sofisticada.

A chave é começar simples (rastreamento básico), depois adicionar validação e estruturas aninhadas. Em produção, sempre considere performance — Proxies são poderosos mas têm custo.

Referências


Artigos relacionados