Reatividade em Vue

Publicados: 2022-03-10
Resumo rápido ↬ Reatividade é a capacidade de uma variável (array, string, número, objeto, etc) se atualizar quando seu valor ou qualquer outra variável à qual ela faz referência é alterada após a declaração.

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

Mais depois do salto! Continue lendo abaixo ↓

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