O Vue.js é provavelmente uma das estruturas JavaScript mais agradáveis. Possui uma API intuitiva, é rápida, flexível e fácil de usar. No entanto, a flexibilidade do Vue.js vem com certos perigos. Alguns desenvolvedores que trabalham com essa estrutura são propensos a pequenos descuidos. Isso pode afetar adversamente o desempenho do aplicativo ou, a longo prazo, a capacidade de suportá-los.

O autor do material, cuja tradução publicamos hoje, oferece a análise de alguns erros comuns cometidos por quem desenvolve aplicativos no Vue.js.
Efeitos colaterais nas propriedades calculadas
As propriedades computadas são um mecanismo Vue.j muito conveniente que permite organizar o trabalho com fragmentos de estado que dependem de outros fragmentos de estado. As propriedades calculadas devem ser usadas apenas para exibir dados armazenados em um estado que depende de outros dados do estado. Se você chamar alguns métodos dentro das propriedades calculadas ou gravar alguns valores em outras variáveis de estado, isso pode significar que você está fazendo algo errado. Considere um exemplo.
export default { data() { return { array: [1, 2, 3] }; }, computed: { reversedArray() { return this.array.reverse();
Se tentarmos derivar
array
e
reversedArray
, perceberemos que ambas as matrizes contêm os mesmos valores.
: [ 3, 2, 1 ] : [ 3, 2, 1 ]
Isso ocorre porque a propriedade reversedArray calculada modifica a propriedade original da
array
chamando seu método
.reverse()
. Este é um exemplo bastante simples que demonstra um comportamento inesperado do sistema. Veja outro exemplo.
Suponha que tenhamos um componente que exibe informações detalhadas sobre o preço de bens ou serviços incluídos em um determinado pedido.
export default { props: { order: { type: Object, default: () => ({}) } }, computed:{ grandTotal() { let total = (this.order.total + this.order.tax) * (1 - this.order.discount); this.$emit('total-change', total) return total.toFixed(2); } } }
Aqui, criamos uma propriedade computada que exibe o custo total do pedido, incluindo impostos e descontos. Como sabemos que o valor total do pedido está mudando aqui, podemos tentar gerar um evento que notifique o componente pai de uma
grandTotal
alteração total.
<price-details :order="order" @total-change="totalChange"> </price-details> export default { // methods: { totalChange(grandTotal) { if (this.isSpecialCustomer) { this.order = { ...this.order, discount: this.order.discount + 0.1 }; } } } };
Agora imagine que, às vezes, embora muito raramente, surjam situações em que trabalhamos com clientes especiais. Damos a esses clientes um desconto adicional de 10%. Podemos tentar alterar o objeto do
order
e aumentar o tamanho do desconto adicionando
0.1
à sua propriedade de
discount
.
Isso, no entanto, levará a um erro grave.
Mensagem de erroCálculo incorreto do valor do pedido para um cliente especialEm uma situação semelhante, ocorre o seguinte: a propriedade calculada é constantemente, em um loop infinito, "recontada". Alteramos o desconto, a propriedade calculada reage a isso, recalcula o custo total do pedido e gera um evento. Ao processar esse evento, o desconto aumenta novamente, isso causa um recálculo da propriedade calculada e assim por diante - até o infinito.
Pode parecer que esse erro não pode ser cometido em um aplicativo real. Mas é mesmo assim? Nosso script (se algo assim acontecer nesta aplicação) será muito difícil de depurar. Tal erro será extremamente difícil de rastrear. O fato é que, para que esse erro ocorra, é necessário que o pedido seja feito por um comprador especial, e um desses pedidos pode ter 1000 pedidos regulares.
Alterando propriedades aninhadas
Às vezes, um desenvolvedor pode ser tentado a editar algo em uma propriedade a partir de
props
, que é um objeto ou uma matriz. Esse desejo pode ser ditado pelo fato de ser muito "simples" fazê-lo. Mas vale a pena? Considere um exemplo.
<template> <div class="hello"> <div>Name: {{product.name}}</div> <div>Price: {{product.price}}</div> <div>Stock: {{product.stock}}</div> <button @click="addToCart" :disabled="product.stock <= 0">Add to card</button> </div> </template> export default { name: "HelloWorld", props: { product: { type: Object, default: () => ({}) } }, methods: { addToCart() { if (this.product.stock > 0) { this.$emit("add-to-cart"); this.product.stock--; } } } };
Aqui temos o componente
Product.vue
, que exibe o nome do produto, seu valor e a quantidade de mercadorias que temos. O componente também exibe um botão que permite ao comprador colocar as mercadorias na cesta. Pode parecer muito fácil e conveniente diminuir o valor da propriedade
product.stock
depois de clicar no botão Fazer isso é realmente simples. Mas se você fizer exatamente isso, poderá encontrar vários problemas:
- Realizamos uma alteração (mutação) da propriedade e não relatamos nada à entidade controladora.
- Isso pode levar a um comportamento inesperado do sistema ou, pior ainda, ao aparecimento de erros estranhos.
- Introduzimos alguma lógica no componente do
product
, que provavelmente não deveria estar presente nele.
Imagine uma situação hipotética em que outro desenvolvedor encontre nosso código pela primeira vez e veja o componente pai.
<template> <Product :product="product" @add-to-cart="addProductToCart(product)"></Product> </template> import Product from "./components/Product"; export default { name: "App", components: { Product }, data() { return { product: { name: "Laptop", price: 1250, stock: 2 } }; }, methods: { addProductToCart(product) { if (product.stock > 0) { product.stock--; } } } };
O pensamento deste desenvolvedor pode ser o seguinte: "Aparentemente, preciso reduzir
product.stock
no método
addProductToCart
"
addProductToCart
Mas se isso for feito, encontraremos um pequeno erro. Se agora pressionar o botão, a quantidade de mercadorias será reduzida não em 1, mas em 2.
Imagine que este é um caso especial quando essa verificação é feita apenas para mercadorias raras ou em conexão com a disponibilidade de um desconto especial. Se esse código entrar em produção, tudo poderá acabar com o fato de que nossos clientes, em vez de 1 cópia do produto, comprarão 2 cópias.
Se este exemplo lhe parecer pouco convincente, imagine outro cenário. Que seja o formulário que o usuário preenche. Passamos a essência do
user
para o formulário como uma propriedade e vamos editar o nome e o endereço de email do usuário. O código mostrado abaixo pode parecer "correto".
É fácil começar a usar o
user
usando a diretiva
v-model
. O Vue.js permite isso. Por que não fazer exatamente isso? Pense nisso:
- E se houver um requisito de que você precise adicionar um botão Cancelar ao formulário, clicando no botão que cancela as alterações feitas?
- E se uma chamada do servidor falhar? Como desfazer alterações no objeto do
user
? - Deseja realmente exibir o nome e o endereço de email alterados no componente pai antes de salvar as alterações correspondentes?
Uma maneira simples de "corrigir" o problema pode ser clonar o objeto de
user
antes de enviá-lo como uma propriedade:
<user-form :user="{...user}">
Embora isso possa funcionar, apenas contornamos o problema, mas não o resolvemos. Nosso componente
UserForm
deve ter seu próprio estado local. Aqui está o que podemos fazer.
<template> <div> <input placeholder="Email" type="email" v-model="form.email"/> <input placeholder="Name" v-model="form.name"/> <button @click="onSave">Save</button> <button @click="onCancel">Save</button> </div> </template> export default { props: { user: { type: Object, default: () => ({}) } }, data() { return { form: {} } }, methods: { onSave() { this.$emit('submit', this.form) }, onCancel() { this.form = {...this.user} this.$emit('cancel') } } watch: { user: { immediate: true, handler: function(userFromProps){ if(userFromProps){ this.form = { ...this.form, ...userFromProps } } } } } }
Embora esse código pareça definitivamente bastante complicado, é melhor que a versão anterior. Ele permite que você se livre dos problemas acima. Esperamos (
watch
) alterações na propriedade do
user
e as copiamos para os dados internos do
form
. Como resultado, o formulário agora tem seu próprio estado e obtemos os seguintes recursos:
- Você pode desfazer as alterações reatribuindo o formulário:
this.form = {...this.user}
. - Temos um estado isolado para o formulário.
- Nossas ações não afetam o componente pai no caso de não precisarmos dele.
- Controlamos o que acontece quando tentamos salvar as alterações.
Acesso direto aos componentes pai
Se um componente se refere a outro componente e executa algumas ações nele, isso pode levar a contradições e erros, o que pode resultar em um comportamento estranho do aplicativo e na aparência de componentes relacionados nele.
Considere um exemplo muito simples - um componente que implementa um menu suspenso. Imagine que temos um componente
dropdown
(pai) e um componente do
dropdown-menu
(filho). Quando o usuário clica em um determinado item de menu, precisamos fechar o
dropdown-menu
. Ocultar e mostrar esse componente é feito pelo componente pai do
dropdown
. Veja um exemplo.
Preste atenção ao método
selectOption
. Embora isso aconteça muito raramente, alguém pode entrar em contato diretamente com
$parent
. Esse desejo pode ser explicado pelo fato de ser muito simples de fazer.
À primeira vista, pode parecer que esse código funcione corretamente. Mas aqui você pode ver alguns problemas:
- E se mudarmos a propriedade
showMenu
ou selectedOption
? O menu suspenso não poderá fechar e nenhum de seus itens será selecionado. - E se você precisar animar o
dropdown-menu
usando algum tipo de transição?
Esse código, novamente, devido a uma alteração no
$parent
, não funcionará. O componente
dropdown
não é mais o pai do
dropdown-menu
. Agora, o pai do
dropdown-menu
é o componente de
transition
.
As propriedades são passadas na hierarquia de componentes, os eventos são passados. Essas palavras contêm o significado da abordagem correta para resolver nosso problema. Aqui está um exemplo modificado para eventos.
Agora, graças ao uso de eventos, o componente filho não está mais vinculado ao componente pai. Podemos alterar livremente propriedades com dados no componente pai e usar transições animadas. No entanto, podemos não pensar em como nosso código pode afetar o componente pai. Simplesmente notificamos esse componente do que aconteceu. Nesse caso, o próprio componente
dropdown
toma decisões sobre como lidar com a escolha do usuário de um item de menu e a operação de fechar o menu.
Sumário
O código mais curto nem sempre é o mais bem-sucedido. As técnicas de desenvolvimento que envolvem resultados "simples e rápidos" geralmente são falhas. Para usar adequadamente qualquer linguagem de programação, biblioteca ou estrutura, você precisa de paciência e tempo. Isso é verdade para Vue.js.
Caros leitores! Você encontrou algum problema na prática, como os discutidos neste artigo?
