Vue.js: Formulários e v-model
Última atualização: 2026-10-20
Os formulários são a interação central das aplicações web — login, cadastro, pesquisa e configurações dependem, todos, do processamento de formulários. O v-model do Vue é um “açúcar sintático” para o processamento de formulários, tornando o código de vinculação bidirecional extremamente conciso (1 linha contra 5 linhas).
O defineModel no Vue 3.4+ simplifica ainda mais a implementação do v-model. Formulários complexos também exigem recursos avançados, como validação, serialização e envio de arquivos. Esta lição aborda cinco cenários principais relacionados a formulários.
1. O que você vai aprender
- Como funciona o v-model (essencialmente, trata-se de um “açúcar sintático” para “props + emit”)
- Componentes personalizados do modelo V
- Simplificação do
defineModel(Vue 3.4+) - Validação de formulários (VeeValidate / Personalizada)
- Design de formulários complexos (campos aninhados/dinâmicos)
- Serialização e envio de formulários
- Envio e visualização de arquivos
2. O pesadelo das “5 seções padrão” em um formulário de login
(1) Desafio: 5 campos de formulário, 10 linhas de código v-model
O formulário de login da Alice tinha 5 campos, cada um com vinculação ao v-model:
5 campos = 10 linhas de texto padrão. 10 campos equivalem a 20 linhas. Quanto mais complexo for o formulário, pior fica a situação.
(2) Abordagem avançada ao v-model do Vue: Objetos reativos + defineModel
5 campos = 1 objeto. A programação reativa reduz a quantidade de código em 50%.
(3) Receita
Após a otimização do modelo V:
- Quantidade de código: 10 linhas → 5 linhas (-50%)
- Novos campos: 1 linha (campos reativos adicionados)
- Enviar o formulário completo: Basta enviar o objeto do formulário
- Validação: Processamento centralizado
3. Como funciona o V-Model
(1) A essência do modelo V
<!-- v-model is props + emit syntax sugar -->
<input v-model="searchQuery">
<!-- equivalent to -->
<input
:value="searchQuery"
@input="searchQuery = $event.target.value"
>
(2) Modelo V em componentes
<!-- Using Parent Components v-model -->
<CustomInput v-model="searchQuery" />
<!-- equivalent to -->
<CustomInput
:modelValue="searchQuery"
@update:modelValue="searchQuery = $event"
/>
(3) defineModel (Simplificado para o Vue 3.4+)
▶ Exemplo: 5 Princípios básicos do modelo V
<!-- 1. text input -->
<input v-model="text">
<!-- 2. textarea -->
<textarea v-model="description"></textarea>
<!-- 3. checkbox -->
<input v-model="agreed" type="checkbox">
<!-- 4. radio -->
<input v-model="gender" type="radio" value="male">
<input v-model="gender" type="radio" value="female">
<!-- 5. select -->
<select v-model="selected">
<option value="vue">Vue</option>
<option value="react">React</option>
</select>
Saída:
Digitar no input atualiza text linha a linha.
Marcar o checkbox → agreed = true; escolher female → gender = 'female'.
Selecionar Vue no select → selected = 'vue' (valor da option).
4. 5 tipos de modelo V personalizado
(1) Dados básicos
(2) mensagens de texto
(3) selecionar
(4) Caixa de seleção (seleções múltiplas)
(5) Modificador personalizado (trim)
<!-- Parent Component:v-model.trim -->
<CustomInput v-model.trim="username" />
<!-- Child component:Processing trim Modifiers -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
let value = e.target.value
if (props.modelModifiers.trim) {
value = value.trim()
}
emit('update:modelValue', value)
}
</script>
▶ Exemplo: 5 Tipos de v-model personalizado
▶ ExperimenteSaída:
O pai escreve <CustomInput v-model="search" />.
Digitar no filho emite update:modelValue e search é atualizado no pai.
É o mesmo que :modelValue="search" mais @update:modelValue="search = $event".
5. Validação de formulários
(1) Validação personalizada
// utils/validators.js
export function isEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}
export function isPhone(value) {
return /^1[3-9]\d{9}$/.test(value)
}
export function minLength(value, min) {
return value.length >= min
}
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.email" @blur="validateField('email')">
<p v-if="errors.email" class="error">{{ errors.email }}</p>
<input v-model="form.password" type="password">
<p v-if="errors.password">{{ errors.password }}</p>
<button :disabled="!isValid">Submit</button>
</form>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { isEmail, minLength } from '@/utils/validators'
const form = reactive({
email: '',
password: ''
})
const errors = reactive({})
const isValid = ref(false)
function validateField(field) {
if (field === 'email') {
if (!form.email) {
errors.email = 'Email is required'
} else if (!isEmail(form.email)) {
errors.email = 'Invalid email format'
} else {
delete errors.email
}
}
if (field === 'password') {
if (!form.password) {
errors.password = 'Password is required'
} else if (!minLength(form.password, 8)) {
errors.password = 'Password must be 8+ characters'
} else {
delete errors.password
}
}
isValid.value = Object.keys(errors).length === 0
}
function handleSubmit() {
Object.keys(form).forEach(validateField)
if (isValid.value) {
console.log('Submit:', form)
}
}
</script>
(2) A biblioteca VeeValidate (recomendada)
npm install vee-validate @vee-validate/rules
<template>
<Form @submit="handleSubmit" :validation-schema="schema">
<Field name="email" type="email" v-model="form.email" />
<ErrorMessage name="email" />
<Field name="password" type="password" v-model="form.password" />
<ErrorMessage name="password" />
<button>Submit</button>
</Form>
</template>
<script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import * as yup from 'yup'
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required()
})
const form = reactive({ email: '', password: '' })
function handleSubmit(values) {
console.log('Valid:', values)
}
</script>
▶ Exemplo: 5 Cenários de validação
// 1. Required
if (!form.email) errors.email = 'Required'
// 2. Email
if (!isEmail(form.email)) errors.email = 'Invalid email'
// 3. Cell Phone
if (!isPhone(form.phone)) errors.phone = 'Invalid phone'
// 4. Length
if (form.password.length < 8) errors.password = 'Too short'
// 5. Confirm Password
if (form.password !== form.confirm) errors.confirm = 'Mismatch'
Saída:
Formulário vazio → errors recebe Required em email e as demais mensagens.
Email malformado mostra Invalid email; telefone inválido mostra Invalid phone.
Senha com menos de 8 caracteres → Too short; confirmação diferente → Mismatch.
6. Criação de formulários complexos
(1) Formulários aninhados
const form = reactive({
user: {
name: '',
email: '',
age: 0
},
address: {
city: '',
country: ''
}
})
(2) Campos dinâmicos (v-for)
const form = reactive({
items: [
{ name: '', quantity: 1, price: 0 }
]
})
function addItem() {
form.items.push({ name: '', quantity: 1, price: 0 })
}
function removeItem(index) {
form.items.splice(index, 1)
}
(3) Os 5 principais modificadores de forma
<!-- 1. .lazy:change Event Trigger(No input) -->
<input v-model.lazy="email">
<!-- 2. .number:Automatically Convert to Numbers -->
<input v-model.number="age" type="number">
<!-- 3. .trim:Automatically Remove Spaces -->
<input v-model.trim="username">
<!-- 4. Custom Modifiers(CustomInput) -->
<CustomInput v-model.trim="text" />
<!-- 5. .debounce(Custom):300ms Image Stabilization -->
<DebouncedInput v-model:debounce="500" />
7. Envio de arquivos
(1) Upload de um único arquivo
(2) Vários arquivos + arrastar e soltar
8. Exercício abrangente: modificadores de v-model
▶ Exemplo: 5 Principais modificadores de forma
<!-- 1. .lazy:change Event -->
<input v-model.lazy="email">
<!-- 2. .number:Automatically Convert to Numbers -->
<input v-model.number="age" type="number">
<!-- 3. .trim:Automatic trim -->
<input v-model.trim="username">
<!-- 4. Combinations of Multiple Modifiers -->
<input v-model.lazy.trim="email">
<!-- 5. Custom Modifiers(Child component)-->
<CustomInput v-model.trim="text" />
Saída:
.lazy só atualiza o dado no evento change (ao sair do campo).
.number converte o texto digitado em número; .trim remove os espaços das pontas.
Em componentes, os modificadores chegam ao filho em defineModel() ou modelModifiers.
(1) 📊 5. Referência rápida para 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| v-model.number ainda é uma string | Erro de adição | type="number" + .number |
| O v-model não é atualizado | A referência fica inativa após a mudança para reativo | Mude para .value ou toRefs |
| Falha no envio do arquivo usando FormData | Erro entre domínios ou de método | fetch + FormData |
| Momento incorreto da validação | Exibir somente quando o foco for perdido | Usar @blur |
| Muito código padrão de campo | código prolixo | usando objetos reativos |
(2) 📊 6. 5 Principais comparações de desempenho
| Padrão | Simplicidade | Desempenho | Aplicabilidade |
|---|---|---|---|
| Várias referências | ❌ Duplicado | ⭐⭐⭐⭐ | 1–2 campos |
| Objeto reativo | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 5 ou mais campos |
| FormData (Manual) | ❌ | ⭐⭐⭐ | Envio de arquivos |
| VeeValidate | ⭐⭐⭐⭐ | ⭐⭐⭐ | Validação complexa |
| defineModel | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Componente Vue 3.4+ |
❓ Perguntas Frequentes
defineProps(['modelValue']) + defineEmits(['update:modelValue']) dentro do componente. Use v-model no componente pai. No Vue 3.4 ou superior, use defineModel para substituir 10 linhas por apenas 1.type="number". Como alternativa, use parseFloat() manualmente.FileReader.readAsDataURL() para ler o arquivo como uma string Base64 e, em seguida, atribua-o a <img src="...">. Use FormData para o envio.change (após perder o foco), o que reduz a sobrecarga da validação em tempo real. O .debounce é um modificador personalizado que deve ser implementado no componente filho; ele aciona o último evento dentro de 300 ms.JSON.stringify(form) (não é necessário usar FormData, a menos que você esteja enviando arquivos). O backend pode simplesmente receber o JSON.defineModel substitui toda a sintaxe do v-model?defineModel('modelValue') is equivalent to the 10 lines of code in props.modelValue + emit('update:modelValue'). É totalmente compatível com versões anteriores.📖 Resumo
v-modelé uma sintaxe simplificada parapropseemit; no Vue 3.4 e versões posteriores, ela é substituída por uma única linha usandodefineModel.- 5 tipos de v-model personalizado: input / textarea / select / checkbox / modificadores
- 5 modificadores: .lazy / .number / .trim / Personalizado / Vários modificadores
- Validação de formulários: Personalizada (simples) / VeeValidate (complexa)
- 5 tipos de formulários complexos: aninhados / dinâmicos / sequenciais / de validação / de arquivo
- Objetos reativos + modelo V reduzem em 50% o código repetitivo
- Envio de arquivos: FileReader + FormData
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Implemente um formulário de login simples:
- 5 campos (nome de usuário / senha / e-mail / telefone / captcha)
- v-model + reativo
- Imprimir todo o objeto do formulário após o envio
-
Problemas avançados (Dificuldade: ⭐⭐)
Implementação da validação de formulários:
- 5 campos + validação personalizada (formato de e-mail, formato de telefone, comprimento da senha)
- @blur aciona a validação
- Mensagem de erro exibida
- O botão “Enviar” fica ativado ou desativado de acordo com a propriedade
isValid
-
Problema desafiador (Dificuldade: ⭐⭐⭐)
Implementar um sistema completo de formulários de “Cadastro de Usuário”:
- 10 campos (incluindo objetos
addressaninhados e uma matriz dinâmicatags) - VeeValidate + esquema do Yup
- Envio de arquivo (foto de perfil)
- Cobertura total de 5 modificadores
defineModelé usado para componentes filhos- 5 Situações de erro
- Tipagem forte do TypeScript
- 10 campos (incluindo objetos