reactiveEffect.ts: O Cor do Sistema Reativo no Vue 3

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.

  1. 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.

  1. 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.

  1. 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.

  1. 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>
  1. 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.

Tags: vue3 Sistema Reativo javascript TypeScript proxy

Publicado em 7-12 21:40