VueJS é um framework web utilizado para construir aplicações front end e é, no momento, amplamente utilizado por desenvolvedores ao redor do mundo.
Ele possui a diretiva v-model
que torna a implementação de two-way data binding em elementos de entrada de dados "mamão com açúcar".
Independentemente do que estiver construindo, você provavelmente vai precisar criar componentes customizados que recebem e manipulam dados através do mecanismo de two-way data binding.
Neste artigo eu vou te mostrar 5 formas diferentes de implementar o seu próprio v-model
em seu componente:
- Watcher local de variáveis
- Método customizado
- Computed Properties "Anabolizadas"
- Prop customizada + evento (VueJS 2)
- O modificador .sync (VueJS 2)
- v-model nomeado (VueJS 3)
Obs.: O objetivo aqui não é comparar a performance nem discutir quais das implementações são melhores ou piores mas sim apresentar diferentes abordagens que podem ser utilizadas para atingir o resultado esperado de um v-model
em componentes customizados.
ℹ O componente chamado BaseInput.vue
usado nos exemplos é bastante simples e você, provavelmente, irá se questionar se implementar o mecanismo de two-way data binding nele é realmente necessário. Porém, como mencionei anteriormente, a intenção aqui é apenas demonstrar as possibilidades.
1. Watcher de variável local
Esta é, de longe, a forma mais comum de se implementar um v-model
em um componente.
Aqui, basta criar uma prop
chamada value
com o tipo desejado, criar uma variável reativa (utilizando a função data()
do componente), inicializá-la com o valor da prop value
definida anteriormente e "observar" suas mudanças utilizando um watcher
.
Cada vez que o watcher identifica uma mudança na variável local, ele emite um evento input
passando o novo valor da mesma. Este valor poderá, então, ser lido pelo componente pai que, por sua vez, irá atualizar a prop value
"de fora para dentro".
<!-- BaseInput.vue -->
<template>
<input type="text" v-model="model" />
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
model: this.value
}
},
watch: {
model(currentValue) {
this.$emit('input', currentValue)
}
}
}
</script>
<!-- Utilização -->
<BaseInput v-model="text" />
2. Método Customizado
Você já deve ter lido por aí que, para prevenir problemas de performance, você deve evitar utilizar muitos watchers na sua aplicação.
Neste segundo exemplo, tendo em mente esta premissa, nós tiramos proveito do evento @input
disparado pelo elemento de entrada (<input />
) nativo* e, utilizando um método customizado dentro do nosso componente, passamos o valor capturado deste elemento para o componente pai através da emissão de um evento input
. O componente pai, por sua vez, ao utilizar o v-model
poderá atualizar a prop value
"de fora para dentro"**.
É importante mencionar que, neste caso, não utilizamos a diretiva v-model
no input nativo, mas sim sua propriedade value
.
* O VueJS já cria, automaticamente, event listeners em elementos de entrada de dados e, quando esses elementos são destruídos, os listeners são destruídos também.
<!-- BaseInput.vue -->
<template>
<input type="text" :value="value" @input="onInput" />
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
methods: {
onInput(event) {
this.$emit('input', event.target.value)
}
}
}
</script>
<!-- Utilização -->
<BaseInput v-model="text" />
⚠ VueJS 3: se você está utilizando a versão mais recente do VueJS, substitua o nome da prop value
por modelValue
e o nome do evento emitido input
por update:modelValue
. Leia mais na documentação do VueJS 3
3. Propriedades Computed "Anabolizadas"
Uma outra forma de implementar seu próprio v-model
é utilizando os getters e setters de computed properties.
Primeiro defina uma computed property local, dentro do seu componente. Depois, implemente um getter que retorna o valor da prop value
e um setter que emite o evento input
para que o componente pai atualize a prop value
"de fora para dentro"**.
<!-- BaseInput.vue -->
<template>
<input type="text" v-model="model" />
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
computed: {
model: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
}
}
}
</script>
<!-- Utilização -->
<BaseInput v-model="text" />
⚠ VueJS 3: se você está utilizando a versão mais recente do VueJS, substitua o nome da prop value
por modelValue
e o nome do evento de input
por update:modelValue
de acordo com a documentação do VueJS 3.
** Você deve evitar a alteração de valor de uma prop diretamente Leia mais na documentação.
4. Prop e Evento Customizados (VueJS 2)
Você deve ter percebido que, nos exemplos anteriores, o nome da prop é sempre value
e o nome do evento é sempre input
. Estes nomes são utilizados por padrão para implementar um v-model
em um componente customizado. Porém, você pode trocá-los de acordo com suas necessidades.
Para que isso seja possível e o mecanismo de two-way data binding continue funcionando você pode utilizar o atributo model
da instância do Vue para informar ao componente o nome da prop e do evento que devem representar "participar" do mecanismo.
<!-- BaseInput.vue -->
<template>
<input type="text" :value="text" @input="onInput" />
</template>
<script>
export default {
model: {
prop: 'text',
event: 'update'
},
props: {
text: {
type: String,
default: ''
}
},
methods: {
onInput(event) {
this.$emit('update', event.target.value)
}
}
}
</script>
<!-- Usage -->
<BaseInput v-model="text" />
⚠ VueJS 3: se você está usando a última versão do VueJS, esta abordagem não irá funcionar pois está desatualizada.
5. O modificador ".sync" (VueJS 2)
Esta não é, exatamente, uma implementação de um v-model
mas irá funcionar de forma similar.
Utilizando o modificador .sync
(VueJS 2.3+), o componente filho, ao invés de utilizar a prop value
, irá utilizar o nome da prop que está sendo "sincronizada" com o componente pai.
Além disso, ao invés de emitir um evento input
para atualizar a prop
, você emite um evento convenientemente chamado update:text
(Source: VueJS - prop.sync) .
<!-- BaseInput.vue -->
<template>
<input type="text" :value="text" @input="onInput" />
</template>
<script>
export default {
props: {
text: {
type: String,
default: ''
}
},
methods: {
onInput(event) {
this.$emit('update:text', event.target.value)
}
}
}
</script>
<!-- Utilização -->
<BaseInput :text.sync="text" />
⚠ VueJS 3: se você está utilizando a versão mais recente do VueJS, esta abordagem não irá funcionar visto que está descontinuada
6. v-model nomeado (VueJS 3)
A versão 3 do VueJS, lançada em 18 de setembro de 2020, tornou possível definir facilmente qual prop
vai representar o v-model
dentro de um componente.
Para fazer isso, basta utilizar um modificador no próprio v-model
quando utilizar o seu componente customizado.
No exemplo abaixo, estamos dizendo que a propriedade text
, dentro do componente BaseInput
, irá receber o valor vindo do v-model
.
<!-- BaseInput.vue -->
<template>
<input type="text" :value="text" @input="onInput" />
</template>
<script>
export default {
model: {
prop: 'text',
event: 'update'
},
props: {
text: {
type: String,
default: ''
}
},
methods: {
onInput(event) {
this.$emit('update', event.target.value)
}
}
}
</script>
<!-- Utilização -->
<BaseInput v-model:text="text" />
Deixe seu comentário me dizendo se conhece alguma outra implementação envolvendo v-model
que seja interessante mencionar aqui ou envie-me sugestões de outros assuntos que podem se tornar um artigo como este.
Você pode encontrar exemplos de todas as abordagens aqui citadas (em inglês) neste repositório.
Muito Obrigado a Keith Machado pela colaboração! (Ver artigo original (inglês))
Espero que seja útil e, por favor, compartilhe!
Top comments (0)