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.