reactiveEffect.ts: O Cor do Sistema Reativo no Vue 3
O sistema reativo do Vue 3 é construído com base nas APIs Proxy e Reflect, onde o reactiveEffect desempenha um papel central, implemantando o rastreamento de mudanças em dados reativos e a atualização automática da visualização. Neste capítulo, exploraremos em profundidade o princípio de funcionamento do reactiveEffect.
- O que é o reactiveEffect?
No Vue, sempre que manipulamos dados reativos, o reactiveEffect é acionado para rastrear essas operações. Funções como atributos computados, observadores (watch) e até a função de renderização do componente são consideradas funções de efeito colateral e são gerenciadas pelo reactiveEffect.
- Mecanismos Core
2.1 Coleta de Dependências (Track)
A função track é responsável por coletar todas as dependências dos dados reativos acessados durante a primeira execução da função de efeito. Isso significa que, quando um dado é lido, todas as funções de efeito dependentes são registradas.
2.2 Disparo de Atualizações (Trigger)
Quando um dado reativo muda, a função trigger identifica todas as funções de efeito dependentes e as executa novamente, garantindo a atualização automática da visualização.
2.3 Escopo de Efeito (effectScope)
O effectScope é um conceito introduzido no Vue 3 que permite organizar e gerenciar múltiplos reactiveEffect. Isso é útil para limpar efeitos colaterais durante a desmontagem de componentes, evitando vazamentos de memória.
- Análise do Código Fonte — Track
3.1 A Função Track
/**
* A função track rastreia o acesso a propriedades de um objeto reativo.
*
* Seu propósito principal é determinar qual função de efeito está em execução
* e registrar essa função como uma dependência da propriedade reativa acessada.
*
* @param alvo - O objeto reativo ao qual a propriedade pertence.
* @param tipo - O tipo de acesso, definido pela enumeração TrackOpTypes (ex: leitura, escrita).
* @param chave - O identificador da propriedade reativa acessada.
*/
export function rastrear(alvo: object, tipo: TrackOpTypes, chave: unknown) {
// Verifica se a coleta de dependências está habilitada e se há uma função de efeito ativa
if (deveRastrear && efeitoAtivo) {
// Obtém ou cria um mapa de dependências para o objeto alvo
let mapaDeDependencias = mapaDeAlvos.get(alvo)
if (!mapaDeDependencias) {
mapaDeAlvos.set(alvo, (mapaDeDependencias = new Map()))
}
// Obtém ou cria um conjunto de dependências para a chave específica
let conjuntoDep = mapaDeDependencias.get(chave)
if (!conjuntoDep) {
mapaDeDependencias.set(chave, (conjuntoDep = criarDep(() => mapaDeDependencias!.delete(chave))))
}
// Registra o efeito ativo no conjunto de dependências
registrarEfeito(
efeitoAtivo,
conjuntoDep,
__DEV__
? {
alvo,
tipo,
chave,
}
: void 0,
)
}
}
3.2 O que é o parâmetro alvo?
Em baseHandlers.ts, no manipulador get, o alvo refere-se ao objeto original, enquanto na função track, é o objeto reativo ao qual a propriedade pertence. No entanto, ambos apontam para o mesmo valor.
3.2.1 Definição de alvo
Considere este exemplo com Proxy:
const alvo = {
foo: 'bar'
}
const manipulador = {
get(alvoDaArmadilha, propriedade, receptor) {
console.log(alvoDaArmadilha === alvo);
console.log(propriedade);
console.log(receptor === proxy);
}
}
const proxy = new Proxy(alvo, manipulador);
proxy.foo;
// Saída:
// true
// foo
// true
Portanto, tecnicamente, tanto no manipulador get quanto na função track, o alvo é o objeto original.
3.2.2 Entendendo o alvo
Veja um exemplo básico de Proxy:
const alvo = {
id: 'alvo'
};
const manipulador = {};
const proxy = new Proxy(alvo, manipulador);
// As propriedades acessam o mesmo valor
console.log(alvo.id); // alvo
console.log(proxy.id); // alvo
// Atribuir ao alvo reflete em ambos os objetos
alvo.id = 'foo';
console.log(alvo.id); // foo
console.log(proxy.id); // foo
3.2.3 Resumo
Em resumo, embora o parâmetro track seja o objeto original, no contexto do sistema reativo do Vue, podemos tratá-lo como seu proxy reativo, pois todas as operações são realizadas por meio desse proxy.
- Análise do Código Fonte — Trigger
/**
* A função trigger encontra todas as funções de efeito dependentes
* e as executa para atualizar a visualização ou outros efeitos.
*
* @param alvo - O objeto reativo que mudou.
* @param tipo - O tipo de mudança, definido pela enumeração TriggerOpTypes (ex: SET, ADD, DELETE).
* @param chave - A propriedade específica que mudou.
*/
export function disparar(
alvo: object,
tipo: TriggerOpTypes,
chave?: unknown,
novoValor?: unknown,
valorAntigo?: unknown,
alvoAntigo?: Map<unknown unknown=""> | Set<unknown>,
) {
// Obtém o mapa de dependências para o objeto alvo
const mapaDeDependencias = mapaDeAlvos.get(alvo)
if (!mapaDeDependencias) {
return // Nunca foi rastreado
}
// Determina quais conjuntos de dependências precisam ser disparados
let deps: (Dep | undefined)[] = []
if (tipo === TriggerOpTypes.CLEAR) {
// Limpeza de coleção: dispara todos os efeitos
deps = [...mapaDeDependencias.values()]
} else if (chave === 'length' && ehArray(alvo)) {
const novoComprimento = Number(novoValor)
mapaDeDependencias.forEach((dep, chave) => {
if (chave === 'length' || (!ehSimbolo(chave) && chave >= novoComprimento)) {
deps.push(dep)
}
})
} else {
// Para operações SET, ADD, DELETE
if (chave !== void 0) {
deps.push(mapaDeDependencias.get(chave))
}
// Lógica adicional para chaves de iteração
switch (tipo) {
case TriggerOpTypes.ADD:
if (!ehArray(alvo)) {
deps.push(mapaDeDependencias.get(CHAVE_ITERACAO))
if (ehMap(alvo)) {
deps.push(mapaDeDependencias.get(CHAVE_ITERACAO_MAP))
}
} else if (ehChaveInteira(chave)) {
deps.push(mapaDeDependencias.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!ehArray(alvo)) {
deps.push(mapaDeDependencias.get(CHAVE_ITERACAO))
if (ehMap(alvo)) {
deps.push(mapaDeDependencias.get(CHAVE_ITERACAO_MAP))
}
}
break
case TriggerOpTypes.SET:
if (ehMap(alvo)) {
deps.push(mapaDeDependencias.get(CHAVE_ITERACAO))
}
break
}
}
// Dispara as atualizações das dependências
pausarAgendamento()
for (const dep of deps) {
if (dep) {
dispararEfeitos(
dep,
NivelDeSujeira.Sujo,
__DEV__
? {
alvo,
tipo,
chave,
novoValor,
valorAntigo,
alvoAntigo,
}
: void 0,
)
}
}
resetarAgendamento()
}
</unknown></unknown>
- Cenários de Uso
5.1 Renderização de Componentes
O processo de renderização de um componente Vue é, em si, um efeito colateral. O Vue utiliza o reactiveEffect para renderizar automaticamente o componente quando os dados reativos dependentes mudam.
5.2 Atributos Computados
Atributos computados são valores derivados de outros dados reativos. Internamente, o Vue usa o reactiveEffect para rastrear as dependências do atributo computado, garantindo atualizações quando os dados dependentes mudam.
5.3 Observadores (Watch)
A API watch do Vue permite observar mudanças em dados reativos e executar funções de retorno de chamada. Isso também é implementado por meio do reactiveEffect.