Reatividade em Vue
Publicados: 2022-03-10Neste artigo, veremos a reatividade no Vue, como ela funciona e como podemos criar variáveis reativas usando métodos e funções recém-criados. Este artigo é direcionado a desenvolvedores que têm uma boa compreensão de como o Vue 2.x funciona e desejam se familiarizar com o novo Vue 3.
Vamos construir um aplicativo simples para entender melhor este tópico. O código para este aplicativo pode ser encontrado no GitHub.
Por padrão, o JavaScript não é reativo . Isso significa que, se criarmos a variável boy
e fizermos referência a ela na parte A do nosso aplicativo, prosseguiremos para modificar boy
na parte B, a parte A não será atualizada com o novo valor de boy
.
let framework = 'Vue'; let sentence = `${framework} is awesome`; console.log(sentence) // logs "Vue is awesome" framework = 'React'; console.log(sentence) //should log "React is awesome" if 'sentence' is reactive.
O trecho acima é um exemplo perfeito da natureza não reativa do JavaScript — por isso, a mudança não é refletida na variável de sentence
.
No Vue 2.x, props
, computed
e data()
eram todos reativos por padrão, com exceção das propriedades que não estão presentes nos data
quando tais componentes são criados. Isso significa que quando um componente é injetado no DOM, apenas as propriedades existentes no objeto de data
do componente fariam com que o componente fosse atualizado se e quando tais propriedades fossem alteradas.
Internamente, o Vue 3 usa o objeto Proxy
(um recurso do ECMAScript 6) para garantir que essas propriedades sejam reativas, mas ainda oferece a opção de usar Object.defineProperty
do Vue 2 para suporte ao Internet Explorer (ECMAScript 5). Esse método define uma nova propriedade diretamente em um objeto ou modifica uma propriedade existente em um objeto e retorna o objeto.
À primeira vista e como a maioria de nós já sabe que reatividade não é novidade no Vue, pode parecer desnecessário fazer uso dessas propriedades, mas a API Options tem suas limitações quando se trata de uma aplicação grande com funções reutilizáveis em vários partes do aplicativo. Para este fim, a nova API de composição foi introduzida para ajudar na abstração da lógica, a fim de tornar uma base de código mais fácil de ler e manter. Além disso, agora podemos facilmente tornar qualquer variável reativa, independentemente de seu tipo de dados, usando qualquer uma das novas propriedades e métodos.
Quando usamos a opção de setup
, que serve como ponto de entrada para a API de composição, o objeto de data
, as propriedades computed
e os methods
ficam inacessíveis porque a instância do componente ainda não foi criada quando a setup
é executada. Isso impossibilita o aproveitamento da reatividade integrada em qualquer um desses recursos na setup
. Neste tutorial, vamos aprender sobre todas as maneiras de fazer isso.
O método reativo
De acordo com a documentação, o método reactive
, que é o equivalente a Vue.observable()
no Vue 2.6, pode ser útil quando estamos tentando criar um objeto cujas propriedades sejam todas reativas (como o objeto de data
no Options API). Nos bastidores, o objeto de data
na API de opções usa esse método para tornar todas as propriedades nele reativas.
Mas podemos criar nosso próprio objeto reativo assim:
import { reactive } from 'vue' // reactive state let user = reactive({ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "[email protected]", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" }, "cars": { "number": 0 } })
Aqui, importamos o método reactive
do Vue e declaramos nossa variável de user
passando seu valor para esta função como um argumento. Ao fazer isso, tornamos o user
reativo e, portanto, se usarmos user
em nosso modelo e se o objeto ou uma propriedade desse objeto mudar, esse valor será atualizado automaticamente nesse modelo.
ref
Assim como temos um método para tornar os objetos reativos, também precisamos de um para tornar os outros valores primitivos autônomos (strings, booleanos, valores indefinidos, números, etc.) e arrays reativos. Durante o desenvolvimento, trabalharíamos com esses outros tipos de dados enquanto também precisávamos que eles fossem reativos. A primeira abordagem que poderíamos pensar seria usar reactive
e passar o valor da variável que queremos tornar reativa.
import { reactive } from 'vue' const state = reactive({ users: [], });
Como reactive
tem conversão reativa profunda, user
como propriedade também seria reativo, atingindo assim nosso objetivo; portanto, user
sempre atualizaria em qualquer lugar em que fosse usado no modelo de tal aplicativo. Mas com a propriedade ref
, podemos tornar qualquer variável com qualquer tipo de dado reativa passando o valor dessa variável para ref
. Esse método também funciona para objetos, mas aninha o objeto em um nível mais profundo do que quando o método reactive
é usado.
let property = { rooms: '4 rooms', garage: true, swimmingPool: false } let reactiveProperty = ref(property) console.log(reactiveProperty) // prints { // value: {rooms: "4 rooms", garage: true, swimmingPool: false} // }
Sob o capô, ref
pega esse argumento passado para ele e o converte em um objeto com uma chave de value
. Isso significa que podemos acessar nossa variável chamando variable.value
e também podemos modificar seu valor chamando-a da mesma maneira.
import {ref} from 'vue' let age = ref(1) console.log(age.value) //prints 1 age.value++ console.log(age.value) //prints 2
Com isso, podemos importar ref
para nosso componente e criar uma variável reativa:
<template> <div class="home"> <form @click.prevent=""> <table> <tr> <th>Name</th> <th>Username</th> <th>email</th> <th>Edit Cars</th> <th>Cars</th> </tr> <tr v-for="user in users" :key="user.id"> <td>{{ user.name }}</td> <td>{{ user.username }}</td> <td>{{ user.email }}</td> <td> <input type="number" name="cars" v-model.number="user.cars.number" /> </td> <td> <cars-number :cars="user.cars" /> </td> </tr> </table> <p>Total number of cars: {{ getTotalCars }}</p> </form> </div> </template> <script> // @ is an alias to /src import carsNumber from "@/components/cars-number.vue"; import axios from "axios"; import { ref } from "vue"; export default { name: "Home", data() { return {}; }, setup() { let users = ref([]); const getUsers = async () => { let { data } = await axios({ url: "data.json", }); users.value = data; }; return { users, getUsers, }; }, components: { carsNumber, }, created() { this.getUsers(); }, computed: { getTotalCars() { let users = this.users; let totalCars = users.reduce(function(sum, elem) { return sum + elem.cars.number; }, 0); return totalCars; }, }; </script>
Aqui, importamos ref
para criar uma variável users
reativa em nosso componente. Em seguida, importamos axios
para buscar dados de um arquivo JSON na pasta public
e importamos nosso componente carsNumber
, que criaremos mais tarde. A próxima coisa que fizemos foi criar uma variável de users
reativa usando o método ref
, para que users
possam atualizar sempre que a resposta do nosso arquivo JSON for alterada.
Também criamos uma função getUser
que busca o array users
do nosso arquivo JSON usando axios e atribuímos o valor dessa solicitação à variável users
. Por fim, criamos uma propriedade computada que calcula o número total de carros que nossos usuários têm conforme a modificamos na seção de modelos.
É importante notar que ao acessar as propriedades ref
que são retornadas na seção de template ou fora de setup()
, elas são automaticamente desempacotadas superficialmente. Isso significa que refs
que são um objeto ainda precisariam de um .value
para serem acessados. Como users
é um array, poderíamos simplesmente usar users
e não users.value
em getTotalCars
.
Na seção de modelo, exibimos uma tabela que exibe as informações de cada usuário, juntamente com um componente <cars-number />
. Este componente aceita um prop cars
que é exibido na linha de cada usuário como o número de carros que eles possuem. Esse valor é atualizado sempre que o valor de cars
muda no objeto de usuário , que é exatamente como o objeto de data
ou a propriedade computed
funcionaria se estivéssemos trabalhando com a API de opções.
toRefs
Quando usamos a API de composição, a função setup
aceita dois argumentos: props
e context
. Este props
é passado do componente para setup()
, e possibilita acessar as props que o componente possui dentro desta nova API. Este método é particularmente útil porque permite a desestruturação de objetos sem perder sua reatividade.
<template> <p>{{ cars.number }}</p> </template> <script> export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { console.log(props); // prints {gender: "female", cars: Proxy} }, }; </script> <style></style>
Para usar um valor que é um objeto de props
na API de composição, garantindo que ele mantenha sua reatividade, usamos toRefs
. Este método pega um objeto reativo e o converte em um objeto simples no qual cada propriedade do objeto reativo original se torna uma ref
. O que isso significa é que os cars
sustentam…
cars: { number: 0 }
… agora se tornaria isso:
{ value: cars: { number: 0 }
Com isso, podemos fazer uso de cars
dentro de qualquer parte da API de configuração mantendo sua reatividade.
setup(props) { let { cars } = toRefs(props); console.log(cars.value); // prints {number: 0} },
Podemos observar essa nova variável usando o watch
da API de composição e reagir a essa alteração da maneira que quisermos.
setup(props) { let { cars } = toRefs(props); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }
toRef
Outro caso de uso comum com o qual podemos nos deparar é passar um valor que não é necessariamente um objeto, mas sim um dos tipos de dados que funcionam com ref
(array, number, string, boolean, etc.). Com toRef
, podemos criar uma propriedade reativa (ou seja, ref
) de um objeto reativo de origem. Isso garantiria que a propriedade permanecesse reativa e seria atualizada sempre que a origem pai fosse alterada.
const cars = reactive({ Toyota: 1, Honda: 0 }) const NumberOfHondas = toRef(state, 'Honda') NumberOfHondas.value++ console.log(state.Honda) // 1 state.Honda++ console.log(NumberOfHondas.value) // 2
Aqui, criamos um objeto reativo usando o método reactive
, com as propriedades Toyota
e Honda
. Também usamos toRef
para criar uma variável reativa fora do Honda
. A partir do exemplo acima, podemos ver que quando atualizamos Honda
usando o objeto cars
reativos ou NumberOfHondas
, o valor é atualizado em ambas as instâncias.
Esse método é semelhante e tão diferente do método toRefs
que abordamos acima no sentido de que ele mantém sua conexão com sua fonte e pode ser usado para strings, arrays e números. Ao contrário de toRefs
, não precisamos nos preocupar com a existência da propriedade em sua fonte no momento da criação, pois se esta propriedade não existir no momento em que esta ref
é criada e ao invés retornar null
, ela ainda seria armazenada como uma propriedade válida, com uma forma de watcher
implementada, de modo que quando esse valor mudar, essa ref
criada usando toRef
também seja atualizada.
Também podemos usar esse método para criar uma propriedade reativa de props
. Isso ficaria assim:
<template> <p>{{ cars.number }}</p> </template> <script> import { watch, toRefs, toRef } from "vue"; export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { let { cars } = toRefs(props); let gender = toRef(props, "gender"); console.log(gender.value); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }, }; </script>
Aqui, criamos um ref
que seria baseado na propriedade gender
obtida de props
. Isso é útil quando queremos realizar operações extras na prop de um componente específico.
Conclusão
Neste artigo, vimos como a reatividade no Vue funciona usando alguns dos métodos e funções recém-introduzidos do Vue 3. Começamos analisando o que é reatividade e como o Vue faz uso do objeto Proxy
nos bastidores para conseguir isso. Também vimos como podemos criar objetos reativos usando reactive
e como criar propriedades reativas usando ref
.
Finalmente, vimos como converter objetos reativos em objetos simples, cada uma das quais propriedades são uma ref
apontando para a propriedade correspondente do objeto original, e vimos como criar uma ref
para uma propriedade em um objeto de origem reativa.
Recursos adicionais
- “Proxy” (objeto), MDN Web Docs
- “Fundamentos de Reatividade”, Vue.js
- “Refs”, Vue.js
- “Lifecycle Hook Registration Inside
setup
”, Vue.js