Vue3: como declarar estados reativos
Tags: braziliandevs, vue, frontend, webdev
Published: 2024-03-06 20:30:46

> <small>_[Read it in English](https://dev.to/sucodelarangela/vue3-how-to-declare-reactive-states-18hc)_</small>

Em uma **Single Page Application (SPA)**, a reatividade é a capacidade que o aplicativo tem de responder dinamicamente a alterações em seus dados, atualizando partes da página sem a necessidade de recarregá-la do zero.

Quando o Vue identifica mudanças em seus estados, ele usa o virtual DOM para re-renderizar a interface e garantir que ela sempre esteja atualizada, tornando o processo o mais rápido e eficiente possível.

Existem diferentes formas de declarar estados reativos em uma aplicação Vue, que podem variar dependendo da API que estiver em uso (Options ou Composition), além dos tipos de dados em si. E é isso que vamos abordar nesse artigo!

> _**Sumário**
> Dados reativos com Options API:
> [Data properties](#data-properties)
> [Computed Properties](#computed-properties)
> [Diferença entre métodos e propriedades computadas](#diferença-entre-métodos-e-propriedades-computadas)
> [Dados reativos com Composition API](#dados-reativos-com-composition-api)
> [ref()](#ref)
> [reactive()](#reactive)
> [Limitações do reactive()](#limitações-do-reactive)
> [computed()](#computed)
> [Em resumo...](#em-resumo)_

![Reação química](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bsu913siqftcjwhax9t.gif)

---

## Dados reativos com Options API

### _Data properties_

Com a Options API, declaramos estados reativos de um componente através das **propriedades de dados** (_data properties_), utilizando o método `data` para tal. Esse método <mark>retorna um objeto com todos os estados reativos e seus valores</mark>.

```vue
<script>
export default {
  data () {
    counter: 0
  }
}
</script>
```
No exemplo acima, `counter` é um dado reativo e, caso seja alterado, a interface atualizará automaticamente para refletir as alterações.

Para vermos a reatividade em ação, vamos criar um método (ou função) que modifica o valor de `counter` cada vez que clicarmos um botão.

```vue
<script>
export default {
  data () {
    counter: 0
  },
  methods: {
    increaseCounter() {
      this.counter++
    }
  }
}
</script>
```

![Counter atualizando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2eq8ic66306ayz35qpex.gif)

Veja que, ao clicar no botão, o valor de `counter` é automaticamente atualizado na interface.

É importante que todos os dados reativos a serem utilizados no componente sejam declarados na opção `data` mesmo que ainda não tenham um valor definido, pois eles serão instanciados no componente assim que ele for criado (proporcionando o uso de `this` em métodos e [ciclos de vida](https://dev.to/sucodelarangela/vuejs-ciclo-de-vida-dos-componentes-2ko9)).

Caso precise iniciar um estado sem valor definido, você pode usar `undefined`, `null` ou agum outro valor que sirva de _placeholder_ (como uma _string_ vazia, por exemplo).

---

### _Computed Properties_

As **propriedades computadas** (_computed properties_) são utilizadas quando precisamos <mark>fazer cálculos ou outras lógicas baseadas em `data properties` de forma passiva</mark>. Ou seja, quando um dado reativo é alterado, todas as propriedades computadas que dependem desse dado também são atualizadas. É como se elas fossem funções _formatadoras_ de variáveis.

Para declarar propriedades computadas, usamos a sintaxe de função dentro da opção `computed`. Essa função deve **sempre retornar um valor**.

```vue
<script>
  export default {
    // Código oculto
    computed: {
      doubleCounter() {
        return this.counter * 2
      }
    }
  }
</script>
```

Note que não utilizamos a sintaxe de _arrow function_ em nossa propriedade computada, pois estas não possuem `this` em seu contexto, impossibilitando que possamos acessar nossas _data properties_.

A propriedade computada acima retorna sempre o dobro do valor de `counter`. Podemos ver em nossa aplicação que, sempre que `counter` for modificado, o valor de `doubleCounter` é automaticamente atualizado: 

![doubleCounter atualizando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nz9rr32zht4edx4md4h4.gif)

O mesmo resultado poderia ser obtido diretamente no `template` do nosso componente inserindo a lógica dentro da interpolação (`{{ }}` também chamada de _mustache_, nosso bom "bigodinho").

```vue
<template>
  <p>The double of counter is: {{ doubleCounter }}</p>
  <!-- VIRARIA -->
  <p>The double of counter is: {{ counter * 2 }}</p>
<template>
```

Dessa forma, nossa aplicação funcionaria da mesma maneira. Porém, em aplicações maiores essas lógicas podem ficar bem mais complexas e, com o uso das propriedades computadas, podemos deixar nosso `template` mais fácil de entender e nosso código mais organizado.

---

### Diferença entre métodos e propriedades computadas

É fácil perceber que a mesma lógica de `doubleCounter` poderia ser alcançada através de um método em vez de uma propriedade computada, concorda?

```vue
<template>
  <p>{{ doubleCounter() }}</p>
<template>

<script>
// código oculto
  methods: {
    doubleCounter() {
      return this.counter * 2
    }
  }
</script>
```

Com o código acima, poderíamos facilmente obter o mesmo resultado. E por que não optamos por essa alternativa? Porque propriedades computadas são armazenadas em _cache_ com base nos dados reativos dos quais depende.

**_E o que isso significa?_**

Significa que, enquanto `counter` não sofrer modificação, `doubleCounter` também não mudará e sempre retornará o último resultado computado independente de quantas vezes você re-renderizar o seu componente. Por outro lado, métodos sempre serão executados quando o componente for re-renderizado, mesmo que nosso dado reativo `counter` não mude.

> Porque precisamos de _caching_? Imagine que temos uma enorme lista como propriedade computada, que faz _loops_ em um _array_ enorme e faz vários cálculos. E então temos outras propriedades computadas que dependem dessa lista. Sem _caching_, executaríamos o _getter_ da lista muito mais vezes que o necessário (quando podemos executar apenas quando a lista sofrer alterações)! — [Extraído da documentação do Vue.](https://vuejs.org/guide/essentials/computed.html#computed-caching-vs-methods)

Dessa forma, vamos usar métodos apenas quando não precisarmos de _caching_! 😉

---

## Dados reativos com Composition API

No Vue 3, a reatividade é alcançada através do sistema conhecido como "Reatividade _Proxy-based_", que cria [_proxies_](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Proxy) em torno dos objetos de dados e permitem interceptar operações de leitura (_get_) e escrita (_set_) nas propriedades desses objetos. Este sistema é uma evolução do sistema de reatividade utilizado nas versões anteriores do Vue (que utilizavam `Object. defineProperty`), proporcionando um desempenho mais eficiente e com menos limitações.

Dessa forma, devido a essa atualização, com a Composition API temos formas diferentes de declarar estados reativos. Aqui saem as _data properties_ e entram as funções `ref()` e `reactive()` na jogada!

---

### _ref()_

Para declarar dados reativos usando a função `ref()`, devemos usar a sintaxe de declaração de variáveis com `const`. `ref()` é comumente utilizado para estados com tipos primitivos (como `string`, `number`, `boolean`, etc.). Vamos utilizar o mesmo exemplo de `counter`:

```vue
<script setup>
  import { ref } from "vue";

  const counter = ref(0);

  const increaseCounter = () => {
    counter.value++;
  };
</script>
```

Quando estudamos as [diferenças entre Options e Composition API](https://dev.to/sucodelarangela/vue3-options-api-vs-composition-api-1j09), vimos que precisamos importar as funções nativas do Vue que vamos utilizar em nosso componente. Então, começamos importando `ref` e usando-o em nossa variável `counter` para englobar o valor inicial do nosso estado.

Para alterar o valor de `counter`, criamos uma _arrow function_ que modifica a propriedade `.value` da nossa `ref`. Essa propriedade é criada automaticamente pelo sistema de reatividade do Vue 3, não sendo necessário declará-la ao criar nosso estado com `ref()`. E é essa propriedade que permite que o Vue detecte se um estado foi acessado ou alterado.

![Counter atualizando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwn5oegr6lzxedcnwtyd.gif)

Note que, quando usamos nosso estado computado diretamente no `template`, não precisamos usar a propriedade `.value`.

---

### _reactive()_

Reactive é a segunda forma que o Vue disponibiliza para declaração de dados reativos. Diferente de `ref`, que engloba o valor do dado em um objeto interno com a propriedade `.value`, a função `reactive()` torna dados do tipo objeto em um objeto reativo. Logo, `reactive()` tem a limitação de não aceitar _strings_, números e _booleans_.

Ainda mantendo o exemplo de `counter`, podemos refatorar nosso código para utilizar `reactive()`:

```vue
<script setup>
  import { reactive} from "vue";

  const state = reactive({ counter: 0 });

  const increaseCounter = () => {
    state.counter++;
  };
</script>
```

No código acima, nossa variável `state` agora recebe um objeto `{ counter: 0 }` como dado reativo. Nesse caso, se quisermos alterar o valor de `counter`, podemos acessar diretamente a chave `counter` dentro do objeto `state` no nosso método `increaseCounter`.

![State counter atualizando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6pmeeq66apjcbjotarp.gif)

Note que em nosso `template` também precisamos acessar diretamente `state.counter` para que seu valor seja renderizado corretamente na tela.

---

### Limitações do reactive()

Como dito mais acima, a primeira limitação da função `reactive()` é não aceitar `string`, `number` e `boolean`. Sendo assim, essa função deve ser usada apenas para dados do tipo objeto (como os próprios objetos, _arrays_ e coleções, como `Map` e `Set`).

Também não é possível reatribuir um novo valor ao mesmo objeto reativo, ou seja, não é possível substituir o objeto inteiro por um outro objeto, pois o sistema de reatividade do Vue trabalha diretamente com o acesso às propriedades.

```js
// Não é possível reatribuir o valor de state
let state = reactive({ count: 0 })
state = reactive({ count: 1 })
```

O uso de `reactive()` também não permite desestruturação, pois a referência de conexão da reatividade é perdida:

```js
const state = reactive({ count: 0 })
// count é desconectado de state.count quando desestruturado
let { count } = state
```

---

### _computed()_

O uso de propriedades computadas mantém o mesmo propósito visto anteriormente: executar cálculos e lógicas de forma passiva de acordo com a mudança detectada em uma `ref()` ou `reactive()`.

Porém, com a Composition API, temos uma sintaxe diferente para a criação dessas propriedades computadas: `computed()` agora é uma função que recebe como parâmetro outra função e retorna uma `ref` computada. Vamos ao exemplo:

```vue
<script setup>
import { ref, computed } from "vue";

// código ocultado

const doubleCounter = computed(() => {
  return counter.value * 2;
});
</script>
```

No código acima, declaramos nossa propriedade computada como uma variável e usamos a função `computed()` para englobar uma _arrow function_ que contém nossa lógica.

![doubleCounter atualizando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnubu4j49bxlq640q4xk.gif)

---

## Em resumo...

Exploramos as nuances de criar dados reativos no Vue, mergulhando na sintaxe da Options API e da Composition API. Ao desbravar a criação de propriedades de dados e computadas, tocamos na essência das Options API, onde a familiaridade com _data properties_ e _computed properties_ se revela crucial. Além disso, nos aventuramos na Composition API, onde _ref_, _reactive_ e _computed_ oferecem ferramentas poderosas para estruturar lógica reativa de forma mais modular e concisa.

Agora que você já sabe como declarar dados reativos, ampliando seu repertório para lidar com a reatividade no Vue e construir aplicações mais robustas e eficientes, você está pronto para elevar sua experiência de desenvolvimento Vue para novos patamares. Experimente, inove e desfrute da jornada reativa!