# Aprofundando-se na Reatividade

Agora é o momento de mergulhar fundo! Uma das características mais distintas do Vue é o seu discreto sistema de reatividade. Os modelos de dados são proxies de objetos JavaScript. Quando você os modifica, a view é atualizada. Isso faz com que a administração de estado seja simples e intuitiva, mas também é importante entender como isso funciona para evitar algumas pegadinhas. Nesta seção, vamos nos aprofundar em alguns dos detalhes de baixo nível do sistema de reatividade do Vue.

Assista um vídeo gratuito sobre Aprofundando-se na Reatividade no Vue Mastery

# O Que é Reatividade?

Esse termo aparece na programação com uma certa frequência atualmente, mas o que realmente significa quando as pessoas o dizem? Reatividade é um paradigma da programação que permite nos ajustarmos à mudanças de uma maneira declarativa. O exemplo canônico geralmente mostrado, por ser ótimo, é uma planilha do excel.

Se você colocar o número dois na primeira célula, e o número 3 na segunda e então utilizar o SUM, a planilha te dará o resultado. Até aqui nada demais. Mas se você atualizar o primeiro número, o SUM é automagicamente atualizado.

O JavaScript, geralmente, não funciona assim -- Se escrevêssemos algo semelhante em Javascript:

var val1 = 2
var val2 = 3
var sum = val1 + val2

// sum
// 5

val1 = 3

// sum
// 5
1
2
3
4
5
6
7
8
9
10
11

Ao atualizarmos o primeiro valor, a soma não é ajustada.

Então, como faríamos isso em JavaScript?

  • Detecte quando há uma mudança em algum dos valores
  • Rastreie a função que o modifica
  • Dispare a função de modo que ela possa atualizar o valor final

# Como o Vue Rastreia Essas Mudanças

Quando você passa um objeto Javascript simples para uma aplicação ou instância de componente, como sua opção data, o Vue irá iterar sobre todas as suas propriedades e convertê-las em Proxies (opens new window) utilizando um handler com getters e setters. Isso é uma feature do ES6, somente, mas nós oferecemos uma versão do Vue 3 que utiliza o Object.defineProperty mais antigo, para dar suporte à navegadores IE. Ambas possuem a mesma API, na superfície, mas a versão com Proxy é mais elegante e oferece melhor performance.

A explicação anterior foi breve e requer algum conhecimento de Proxies (opens new window) para ser entendida! Então vamos nos aprofundar um pouco. Há muita literatura sobre Proxies, mas o que você realmente precisa saber é que um Proxy é um objeto que encapsula um outro objeto ou função e permite que você o intercepte.

Nós o utilizamos assim: new Proxy(target, handler)

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop) {
    return target[prop]
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// tacos
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Ok, até agora, estamos apenas encapsulando o objeto e o retornando. Legal, mas ainda não tão útil. Mas observe isso, nós também podemos interceptar esse objeto enquanto temos ele encapsulado no Proxy. Essa interceptação é chamada de armadilha (trap).

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop) {
    console.log(‘interceptado!)
    return target[prop]
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// interceptado!
// tacos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Indo além de um console.log, nós poderíamos fazer aqui qualquer coisa que desejássemos. Poderíamos até mesmo não retornar o valor real se quiséssemos. Isso é o que torna os Proxies tão poderosos para a criação de APIs.

Além disso, há uma outra feature que os Proxies nos oferecem. Ao invés de apenas retornar valores como esse: target[prop], nós poderíamos levar isso um passo adiante e usar uma feature chamada Reflect, que nos permite fazer a interligação apropriada do this. Isso leva à algo parecido com isso:







 









const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop, receiver) {
    return Reflect.get(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// interceptado!
// tacos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Nós mencionamos antes que para ter uma API que atualiza o valor final quando algo é modificado, vamos precisar configurar novos valores quando alguma coisa muda. Nós fazemos isso no handler, em uma função chamada track, onde passamos os parâmetros target e key.







 










const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    return Reflect.get(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// interceptado!
// tacos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Finalmente, nós também configuramos novos valores quando alguma coisa muda. Para isso, vamos configurar as mudanças no nosso novo Proxy, disparando essas mudanças:

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    return Reflect.get(...arguments)
  },
  set(target, key, value, receiver) {
    trigger(target, key)
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// interceptado!
// tacos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Lembra-se desta lista de alguns parágrafos atrás? Agora nós temos algumas respostas sobre como o Vue manipula essas mudanças:

  • Detecte quando há uma mudança em algum dos valores: não precisamos mais fazer isso, uma vez que os Proxies nos permitem interceptar essas mudanças
  • Rastreie a função que o modifica: Nós fazemos isso em um getter dentro do proxy, chamado effect
  • Dispare a função de modo que ela possa atualizar o valor final: Nós fazemos isso em um setter dentro do proxy, chamado trigger

O objeto com o proxy aplicado é invisível para o usuário, mas por baixo dos panos ele possibilita que o Vue faça o rastreamento-de-dependência (dependency-tracking) e a notificação-de-mudança (change-notification) quando propriedades são acessadas ou modificadas. A partir do Vue 3, nossa reatividade está agora disponível em um pacote separado (opens new window). Um problema é que o console de navegadores formatam diferentemente quando objetos de dados convertidos são registrados no log, então pode ser que você queria instalar o vue-devtools (opens new window) para uma interface mais amigável à inspeção.

# Objetos com Proxy Aplicado

O Vue rastreia internamente todos os objetos que foram transformados em reativos, então ele sempre retorna o mesmo proxy de um mesmo objeto.

Quando um objeto aninhado é acessado através de um proxy reativo, esse objeto é também convertido em um proxy antes de ser retornado:

const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    const value = Reflect.get(...arguments)
    if (isObject(value)) {
      return reactive(value)
    } else {
      return value
    }
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12

# Proxy vs. Identidade Original

O uso do Proxy de fato introduz um novo empecilho a ser considerado: o objeto com o proxy aplicado não é igual ao objeto original em termos de comparação de identidade (===). Por exemplo:

const obj = {}
const wrapped = new Proxy(obj, handlers)

console.log(obj === wrapped) // false
1
2
3
4

A versão original e a versão encapsulada se comportarão da mesma forma na maioria dos casos, mas esteja ciente de que elas irão falhar em operações que dependem de comparações fortes de identidade, como .filter() ou .map(). Esse empecilho tem pouca chance de ocorrer quando a API de opções estiver sendo utilizada, porque todo o estado reativo é acessado a partir do this e é garantido que já sejam proxies.

Entretanto, quanto estiver utilizando a API de composição para criar objetos reativos explicitamente, a melhor prática é a de nunca guardar uma referência para o puro objeto original e trabalhar somente com a versão reativa:

const obj = reactive({
  count: 0
}) // nenhuma referência ao original
1
2
3

# Observadores

Toda instância de componente tem uma instância de observador correspondente, que registra quaisquer propriedades "tocadas" durante a renderização do componente como dependências. Depois, quando um setter de uma dependência é disparado, ele notifica o observador, que por sua vez faz o componente re-renderizar.

Quando você passa um objeto para uma instância de um componente como data, o Vue o converte em um proxy. Esse proxy permite ao Vue realizar o rastreamento-de-dependência (dependency-tracking) e a notificação-de-mudança (change-notification), quando as propriedades são acessadas ou modificadas. Cada propriedade é considerada uma dependência.

Após a primeira renderização, um componente teria rastreado uma lista de dependências — as propriedades que acessou durante a renderização. Inversamente, o componente se torna um assinante de cada uma dessas propriedades. Quando um proxy intercepta uma operação set, a propriedade notificará cada um de seus componentes assinantes para re-renderizarem.

Se você está utilizando Vue v2.x ou mais antigo, pode estar interessado em alguns empecilhos da detecção de mudanças que existem nessas versões, explorados em mais detalhes aqui.

Deployed on Netlify.
Atualizado pela última vez: 10/20/2020, 1:28:51 PM