Como funciona a reatividade no Vue 2 e 3

Como funciona a reatividade no Vue 2 e 3

As ferramentas em JavaScript vêm com mecanismos de reatividade. Ao saber o que significa reatividade e como funciona internamente, você pode entender quando seu código funciona e falha. Além de ser capaz de identificar bugs e evitar que eles aconteçam.
Vamos começar examinando a palavra reatividade. O que isso significa?
Anos atrás, os desenvolvedores implementaram HTML puro com JavaScript para obter interações na página. A reatividade no frontend era "arcaica", mas o suficiente para a época.
Devido ao aumento da complexidade nas páginas e aplicativos, esta solução era difícil de manter. Foi quando surgiram as bibliotecas e frameworks, como o Vue.
Em vez de programar diretamente a interação do DOM, essas ferramentas lidam com esse trabalho, enquanto provam uma maneira muito mais limpa de estruturar seu projeto.
Definindo reatividade na comunidade de front-end, ele sempre será anexado a frameworks, como React e Vue. O que eu quero compartilhar aqui não são os frameworks no frontend. E em vez disso, como as coisas funcionam com o Vue.
Este é um código em Vue:
<template>
  <div>
    <div>Price is ${{ price }}</div>
    <div>Quantity is {{ quantity }}</div>
    <div>Total is ${{ price * quantity}}</div>
    <button @click="addQuantity">Add quantity</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      price: 10,
      quantity: 0,
    };
  },
  methods: {
    addQuantity() {
      this.quantity = this.quantity + 1;
    },
  },
};
</script>
O que está acontecendo é que:
  • Temos duas variáveis, o price (sempre igual a 10) e aquantity
  • Ao clicar no botão, uma função addQuantity é chamada para aumentar a variável quantity
  • O total é atualizado na página com price * quantity
O quantity é mais do que apenas uma variável, é um estado. Os estados são como um sistema reativo pode realizar a renderização na página, pois é notificado quando um estado altera.
Vamos dar uma olhada mais de perto na função addQuantity:
addQuantity() {
  this.quantity = this.quantity + 1;
}
Quando comecei com React, estava claro para mim que a função setState era responsável por notificar o motor do React para atualizar um estado no aplicativo (o nome da função já dá uma boa dica).
Mas no Vue, é uma atribuição. E se for JavaScript procedural sendo compilado (e é sim), então deve haver algo oculto que torne o código reativo (e novamente, existe sim).
Então, como o Vue sabe quando o estado de quantity mudou?
Vamos começar com o Vue 2.

Reatividade em Vue 2

A reatividade dos estados no Vue 2 é baseada em Object.defineProperty, uma implementação JavaScript que pode criar um Design Pattern de Observer. Dê uma olhada neste exemplo:
const data = { quantity: 1 };

Object.defineProperty(data, 'quantity', {
  get () {
    console.log('get of quantity was called');
  },
  set () {
    console.log('set of quantity was called');
  }
})

data.quantity; // this will log 'get of quantity was called'
data.quantity = 2; // this will log 'set of quantity was called'
Esta implementação JavaScript cria funções getter e setter. Mas se quisermos mudar o estado de data, precisamos criar uma cópia dele (caso contrário, acessar ou atribuir um valor com essas funções acionaria um loop infinito).
Usando o mesmo exemplo, o dataQuantityCopy armazena o valor:
const data = { quantity: 1 };
let dataQuantityCopy = data.quantity;

Object.defineProperty(data, 'quantity', {
  get () {
    return dataQuantityCopy;
  },
  set (value) {
    dataQuantityCopy = value
  }
})

data.quantity;
data.quantity = 2;
Agora que temos algo próximo a um estado, podemos chamar outras funções ou qualquer reatividade necessária para atualizar algo ao usar get eset de Object.defineProperty.
Uma implementação mais avançada poderia ser alcançada com Object.keys(data).forEach, então não apenas quantidade funcionaria, mas quaisquer dados.
Esta é uma simplificação do que está dentro do motor do Vue. Ao usar o Vue, os desenvolvedores não precisam se preocupar em como implementar o gerenciamento de estado em JavaScript.

Limitações no Vue 2 (armadilhas para estar ciente)

Essa solução funciona bem para valores primitivos, mas e se a propriedade também for um objeto ou um array? E se adicionar novos valores que não existiam antes nos data?
Na estrutura do Vue, existem ressalvas e soluções alternativas para o tipo de objeto e array.
Objetos:
O Vue não pode detectar adição ou exclusão de propriedade. Os getters e setters são definidos apenas na inicialização do componente.
Uma solução alternativa para isso é usar Vue.set, que permite que o Vue saiba que há mudanças na estrutura do objeto e modifique os getters e setters de a propriedade.
Arrays:
O Vue não pode detectar mudanças por índice (exemplo: item[index] = value). O setter não é acionado.
Uma solução alternativa para isso é usar Vue.set ou Array.splice. Ambos deixarão Vue saber que há mudanças.

Que entre o Vue 3

Uma das principais mudanças no motor do Vue relacionada aos estados é que Object.defineProperty foi substituído por Proxy e Reflect.
Usando o mesmo exemplo de antes, o código ainda é semelhante:
const data = { quantity: 1 }
const proxiedData = new Proxy(data, {
  get(target, key) {
    console.log('get of ' + key + ' was called');
    return Reflect.get(target, key) 
  },
  set(target, key, value) {
    console.log('set of ' + key + ' was called');
    return Reflect.set(target, key, value)
  }
})

proxiedData.quantity; // this will log 'get of quantity was called'
proxiedData.quantity = 2; // this will log 'set of quantity was called'
A API do Proxy funciona comoObject.defineProperty. Mas a diferença é que o getter e o setter recebem a chave da propriedade que está sendo modificada, o que torna mais fácil inicializar todo o estado para ser reatividade (assim, novas adições de propriedades são acionadas, eliminando as ressalvas no Vue 2).
A API do Reflect funciona de forma semelhante à avaliação ou atribuição, mas por serem funções, eles sabem como obter e definir os dados sem acionar o Proxy novamente, evitando assim loops infinitos.
Limitações no Vue 3
Essas APIs estão disponíveis em ES6 e bibliotecas polyfill como Babel não suportam transpilações de Proxy.
Isso significa que esses recursos não funcionam em navegadores antigos, então Vue 3 não é compatível com IE11 (veja mais informações nesta RFC).

Conclusão

O Vue 3 vem com melhorias significativas na reatividade em seu motor, sem afetar a API para desenvolvedores. Podemos continuar a usá-lo como antes e sem precisar nos preocupar com as ressalvas anteriores ao lidar com objetos e matrizes.
Essa mudança teve o custo de descartar o suporte para o IE11. Portanto, ao escolher bibliotecas e frameworks para construir um aplicativo, devemos considerar se o suporte a navegadores antigos como o IE11 é relevante, o que pode tornar o Vue 3 não ser a melhor opção.