Implementando v-model em Componentes Vue
Observação importante: O v-model funciona apenas com dados reativos do tipo ref. Não é possível utilizar diretamente com objetos reactive, pois a sincronização automática não ocorre nesse caso.
Abordagem 1: Vinculação Padrrão de um Campo
Criando o componente filho FormField.vue:
<template>
<div class="campo">
<input
type="text"
:value="modelValue"
@input="dispararAtualizacao($event.target.value)"
/>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const propriedades = defineProps({
modelValue: {
type: String,
required: true
}
})
const emitir = defineEmits(['update:modelValue'])
function dispararAtualizacao(valor) {
emitir('update:modelValue', valor)
}
</script>
No componente pai, basta utilizar:
<template>
<div>
<FormField v-model="nomeCompleto" />
<p>Valor atual: {{ nomeCompleto }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import FormField from './FormField.vue'
const nomeCompleto = ref('Digite aqui')
</script>
Aobrdagem 2: Múltiplas Vinculações com v-model Nomeado
Quando o componente precisa gerenciar mais de um campo reativo, utilizamos v-model:nomeDoCampo.
Componente PerfilForm.vue:
<template>
<div>
<input
type="text"
placeholder="Primeiro nome"
:value="nome"
@input="$emit('update:nome', $event.target.value)"
/>
<input
type="text"
placeholder="Sobrenome"
:value="sobrenome"
@input="$emit('update:sobrenome', $event.target.value)"
/>
</div>
</template>
<script setup>
defineProps({
nome: String,
sobrenome: String
})
defineEmits(['update:nome', 'update:sobrenome'])
</script>
Utilizando no componente pai:
<template>
<div>
<PerfilForm v-model:nome="primeiroNome" v-model:sobrenome="ultimoNome" />
<p>Nome completo: {{ primeiroNome }} {{ ultimoNome }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import PerfilForm from './PerfilForm.vue'
const primeiroNome = ref('João')
const ultimoNome = ref('Silva')
</script>
Abordagem 3: Utilizando Propriedade Computada
Uma alternativa elegante é encapsular a lógica de leitura e escrita dentro de um computed com getter/setter.
Componente CampoControlado.vue:
<script setup>
import { computed, defineProps, defineEmits } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const conteudoLocal = computed({
get: () => props.modelValue,
set: (novoValor) => emit('update:modelValue', novoValor)
})
</script>
<template>
<input v-model="conteudoLocal" />
</template>
Essa técnica centraliza toda a comunicação entre pai e filho de forma limpa e reativa.
Modificadores Personalizados para v-model
O Vue permite criar modificadores customizados que transformam o valor durante a sincronização. Por exemplo, vamos implementar um modificador caixaBaixa que converte automaticamente o texto para minúsculas.
Componente EntradaTexto.vue:
<template>
<input
type="text"
:value="modelValue"
@input="aoAlterar($event.target.value)"
/>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
modelValue: String,
modelModifiers: {
type: Object,
default: () => ({})
}
})
const emitir = defineEmits(['update:modelValue'])
function aoAlterar(valor) {
if (props.modelModifiers.caixaBaixa) {
valor = valor.toLowerCase()
}
emitir('update:modelValue', valor)
}
</script>
No componente pai, o modificador é aplicado diretamente na diretiva:
<template>
<div>
<EntradaTexto v-model.caixaBaixa="textoDigitado" />
<p>Conteúdo: {{ textoDigitado }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import EntradaTexto from './EntradaTexto.vue'
const textoDigitado = ref('Exemplo de Texto')
</script>
Dessa forma, sempre que o usuário digitar algo, o valor será automaticamente convertido para letras minúsculas antes de ser atribuído à referência reativa.