v-model
La directiva v-model es uno de los mecanismos más elegantes de Vue.js para crear un enlace bidireccional entre los elementos de formulario y el estado de la aplicación. Esta directiva simplifica enormemente el manejo de datos en formularios, eliminando la necesidad de escribir código repetitivo para sincronizar valores entre la vista y el modelo de datos.
v-model combina internamente el enlace de propiedades :value
y el manejo de eventos @input
, creando lo que conocemos como two-way data binding. Cuando el usuario modifica un campo del formulario, el estado se actualiza automáticamente, y cuando el estado cambia programáticamente, la interfaz se actualiza para reflejarlo.
Sintaxis básica con campos de texto
El uso más común de v-model es con campos de entrada de texto. Observa cómo funciona en este ejemplo práctico:
<template>
<div>
<input v-model="mensaje" placeholder="Escribe algo...">
<p>Has escrito: {{ mensaje }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const mensaje = ref('')
</script>
En este ejemplo, cualquier cambio en el campo de entrada se refleja inmediatamente en el párrafo inferior. La reactividad de Vue.js se encarga de mantener sincronizados ambos elementos sin necesidad de código adicional.
Funcionamiento con diferentes tipos de input
La versatilidad de v-model se hace evidente al trabajar con distintos tipos de elementos de formulario. Cada tipo maneja los datos de manera específica según su naturaleza:
Campos numéricos:
<template>
<div>
<input type="number" v-model.number="edad">
<p>Edad: {{ edad }} (tipo: {{ typeof edad }})</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const edad = ref(0)
</script>
Áreas de texto:
<template>
<div>
<textarea v-model="descripcion" rows="4" cols="50"></textarea>
<p>Descripción: {{ descripcion }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const descripcion = ref('')
</script>
Trabajando con checkboxes y radio buttons
Los elementos de selección como checkboxes y radio buttons tienen comportamientos específicos con v-model que es importante comprender:
Checkbox individual (valor booleano):
<template>
<div>
<input type="checkbox" id="acepta" v-model="aceptaTerminos">
<label for="acepta">Acepto los términos y condiciones</label>
<p>Estado: {{ aceptaTerminos ? 'Aceptado' : 'No aceptado' }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const aceptaTerminos = ref(false)
</script>
Múltiples checkboxes (array de valores):
<template>
<div>
<h3>Selecciona tus tecnologías favoritas:</h3>
<input type="checkbox" id="vue" value="Vue.js" v-model="tecnologias">
<label for="vue">Vue.js</label>
<input type="checkbox" id="react" value="React" v-model="tecnologias">
<label for="react">React</label>
<input type="checkbox" id="angular" value="Angular" v-model="tecnologias">
<label for="angular">Angular</label>
<p>Tecnologías seleccionadas: {{ tecnologias }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const tecnologias = ref([])
</script>
Radio buttons (valor único):
<template>
<div>
<h3>Selecciona tu nivel de experiencia:</h3>
<input type="radio" id="principiante" value="principiante" v-model="nivelExperiencia">
<label for="principiante">Principiante</label>
<input type="radio" id="intermedio" value="intermedio" v-model="nivelExperiencia">
<label for="intermedio">Intermedio</label>
<input type="radio" id="avanzado" value="avanzado" v-model="nivelExperiencia">
<label for="avanzado">Avanzado</label>
<p>Nivel seleccionado: {{ nivelExperiencia }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const nivelExperiencia = ref('')
</script>
Elementos select
Los elementos select ofrecen flexibilidad para crear listas desplegables tanto de selección única como múltiple:
Select de selección única:
<template>
<div>
<select v-model="paisSeleccionado">
<option disabled value="">Selecciona un país</option>
<option value="es">España</option>
<option value="mx">México</option>
<option value="ar">Argentina</option>
<option value="co">Colombia</option>
</select>
<p>País seleccionado: {{ paisSeleccionado }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const paisSeleccionado = ref('')
</script>
Select múltiple:
<template>
<div>
<select v-model="coloresFavoritos" multiple>
<option value="rojo">Rojo</option>
<option value="azul">Azul</option>
<option value="verde">Verde</option>
<option value="amarillo">Amarillo</option>
</select>
<p>Colores seleccionados: {{ coloresFavoritos }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const coloresFavoritos = ref([])
</script>
Modificadores útiles de v-model
Vue.js proporciona modificadores que extienden la funcionalidad básica de v-model para casos específicos:
.lazy
- Sincroniza después del evento 'change' en lugar de 'input'.number
- Convierte automáticamente el valor a número.trim
- Elimina espacios en blanco al inicio y final
<template>
<div>
<!-- Sincronización lazy (solo cuando pierde el foco) -->
<input v-model.lazy="textoLazy" placeholder="Sincronización lazy">
<p>Valor lazy: {{ textoLazy }}</p>
<!-- Conversión automática a número -->
<input v-model.number="precio" type="number" placeholder="Precio">
<p>Precio: {{ precio }} ({{ typeof precio }})</p>
<!-- Eliminación automática de espacios -->
<input v-model.trim="nombreCompleto" placeholder="Nombre completo">
<p>Nombre: "{{ nombreCompleto }}"</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const textoLazy = ref('')
const precio = ref(0)
const nombreCompleto = ref('')
</script>
El dominio de v-model es fundamental para crear formularios eficientes en Vue.js. Esta directiva no solo reduce la cantidad de código necesario, sino que también hace que el manejo de estados en formularios sea más predecible y mantenible, aprovechando al máximo el sistema de reactividad de Vue.js.
Formularios básicos y validaciones simples
Los formularios básicos en Vue.js requieren una estructura organizativa que combine la captura de datos con la validación de los mismos antes de procesarlos. La validación es un aspecto crítico que garantiza la integridad de los datos y mejora la experiencia del usuario proporcionando retroalimentación inmediata sobre errores o campos incompletos.
Estructura de un formulario completo
Un formulario bien estructurado debe manejar múltiples aspectos: captura de datos, validación, envío y manejo de errores. Veamos cómo construir un formulario de registro de usuario que integre estos elementos:
<template>
<form @submit.prevent="enviarFormulario" class="formulario">
<h2>Registro de Usuario</h2>
<div class="campo">
<label for="nombre">Nombre completo:</label>
<input
id="nombre"
v-model="formulario.nombre"
type="text"
:class="{ error: errores.nombre }"
@blur="validarCampo('nombre')"
>
<span v-if="errores.nombre" class="mensaje-error">{{ errores.nombre }}</span>
</div>
<div class="campo">
<label for="email">Email:</label>
<input
id="email"
v-model="formulario.email"
type="email"
:class="{ error: errores.email }"
@blur="validarCampo('email')"
>
<span v-if="errores.email" class="mensaje-error">{{ errores.email }}</span>
</div>
<div class="campo">
<label for="edad">Edad:</label>
<input
id="edad"
v-model.number="formulario.edad"
type="number"
:class="{ error: errores.edad }"
@blur="validarCampo('edad')"
>
<span v-if="errores.edad" class="mensaje-error">{{ errores.edad }}</span>
</div>
<button type="submit" :disabled="!formularioValido">
{{ enviando ? 'Enviando...' : 'Registrar' }}
</button>
<div v-if="mensajeExito" class="mensaje-exito">
{{ mensajeExito }}
</div>
</form>
</template>
<script setup>
import { ref, computed, reactive } from 'vue'
const formulario = reactive({
nombre: '',
email: '',
edad: null
})
const errores = ref({})
const enviando = ref(false)
const mensajeExito = ref('')
</script>
<style scoped>
.formulario {
max-width: 400px;
margin: 0 auto;
}
.campo {
margin-bottom: 1rem;
}
.error {
border-color: #e74c3c;
}
.mensaje-error {
color: #e74c3c;
font-size: 0.875rem;
}
.mensaje-exito {
color: #27ae60;
margin-top: 1rem;
}
</style>
Sistema de validación por campos
La validación por campos permite proporcionar retroalimentación inmediata al usuario sin esperar al envío del formulario. Este enfoque mejora significativamente la experiencia de usuario:
// Continuación del script setup anterior
const validarCampo = (campo) => {
errores.value = { ...errores.value }
switch (campo) {
case 'nombre':
if (!formulario.nombre.trim()) {
errores.value.nombre = 'El nombre es obligatorio'
} else if (formulario.nombre.trim().length < 2) {
errores.value.nombre = 'El nombre debe tener al menos 2 caracteres'
} else {
delete errores.value.nombre
}
break
case 'email':
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!formulario.email.trim()) {
errores.value.email = 'El email es obligatorio'
} else if (!emailRegex.test(formulario.email)) {
errores.value.email = 'El formato del email no es válido'
} else {
delete errores.value.email
}
break
case 'edad':
if (!formulario.edad) {
errores.value.edad = 'La edad es obligatoria'
} else if (formulario.edad < 18) {
errores.value.edad = 'Debes ser mayor de edad'
} else if (formulario.edad > 120) {
errores.value.edad = 'Ingresa una edad válida'
} else {
delete errores.value.edad
}
break
}
}
const validarFormularioCompleto = () => {
validarCampo('nombre')
validarCampo('email')
validarCampo('edad')
}
const formularioValido = computed(() => {
return Object.keys(errores.value).length === 0 &&
formulario.nombre.trim() !== '' &&
formulario.email.trim() !== '' &&
formulario.edad !== null
})
Manejo del envío de formularios
El manejo del envío debe incluir validación final, prevención de envíos duplicados y retroalimentación al usuario sobre el estado de la operación:
// Continuación del script setup
const enviarFormulario = async () => {
// Validar todo el formulario antes de enviar
validarFormularioCompleto()
// Si hay errores, no enviar
if (!formularioValido.value) {
return
}
enviando.value = true
mensajeExito.value = ''
try {
// Simular envío a servidor
await new Promise(resolve => setTimeout(resolve, 2000))
// Limpiar formulario tras éxito
Object.assign(formulario, {
nombre: '',
email: '',
edad: null
})
errores.value = {}
mensajeExito.value = '¡Usuario registrado correctamente!'
} catch (error) {
// Manejo de errores del servidor
errores.value.servidor = 'Error al registrar usuario. Inténtalo de nuevo.'
} finally {
enviando.value = false
}
}
Validaciones en tiempo real
Las validaciones en tiempo real proporcionan una experiencia más fluida al usuario. Podemos implementar validaciones que se ejecuten mientras el usuario escribe:
<template>
<div class="campo">
<label for="password">Contraseña:</label>
<input
id="password"
v-model="formulario.password"
type="password"
:class="{
error: errores.password,
success: formulario.password && !errores.password
}"
@input="validarPasswordEnTiempoReal"
>
<!-- Indicador de fortaleza de contraseña -->
<div v-if="formulario.password" class="indicador-password">
<div class="criterio" :class="{ cumplido: criteriosPassword.longitud }">
✓ Al menos 8 caracteres
</div>
<div class="criterio" :class="{ cumplido: criteriosPassword.mayuscula }">
✓ Una letra mayúscula
</div>
<div class="criterio" :class="{ cumplido: criteriosPassword.numero }">
✓ Un número
</div>
</div>
<span v-if="errores.password" class="mensaje-error">{{ errores.password }}</span>
</div>
</template>
<script setup>
// Añadir al reactive del formulario
const formulario = reactive({
nombre: '',
email: '',
edad: null,
password: ''
})
const criteriosPassword = ref({
longitud: false,
mayuscula: false,
numero: false
})
const validarPasswordEnTiempoReal = () => {
const password = formulario.password
// Actualizar criterios
criteriosPassword.value = {
longitud: password.length >= 8,
mayuscula: /[A-Z]/.test(password),
numero: /\d/.test(password)
}
// Validar y mostrar errores solo si el campo no está vacío
if (password.trim()) {
if (!criteriosPassword.value.longitud) {
errores.value.password = 'La contraseña debe tener al menos 8 caracteres'
} else if (!criteriosPassword.value.mayuscula) {
errores.value.password = 'La contraseña debe tener al menos una mayúscula'
} else if (!criteriosPassword.value.numero) {
errores.value.password = 'La contraseña debe tener al menos un número'
} else {
delete errores.value.password
}
} else {
delete errores.value.password
}
}
</script>
<style scoped>
.success {
border-color: #27ae60;
}
.indicador-password {
margin-top: 0.5rem;
font-size: 0.875rem;
}
.criterio {
color: #e74c3c;
}
.criterio.cumplido {
color: #27ae60;
}
</style>
Validaciones condicionales y dependientes
En ocasiones necesitamos validaciones que dependan de otros campos del formulario. Este patrón es común en formularios de confirmación:
<template>
<div class="campo">
<label for="confirmar-password">Confirmar contraseña:</label>
<input
id="confirmar-password"
v-model="formulario.confirmarPassword"
type="password"
:class="{ error: errores.confirmarPassword }"
@blur="validarConfirmacionPassword"
>
<span v-if="errores.confirmarPassword" class="mensaje-error">
{{ errores.confirmarPassword }}
</span>
</div>
</template>
<script setup>
// Añadir al reactive del formulario
const formulario = reactive({
nombre: '',
email: '',
edad: null,
password: '',
confirmarPassword: ''
})
const validarConfirmacionPassword = () => {
if (!formulario.confirmarPassword.trim()) {
errores.value.confirmarPassword = 'Debes confirmar la contraseña'
} else if (formulario.password !== formulario.confirmarPassword) {
errores.value.confirmarPassword = 'Las contraseñas no coinciden'
} else {
delete errores.value.confirmarPassword
}
}
// Actualizar la validación del formulario completo
const validarFormularioCompleto = () => {
validarCampo('nombre')
validarCampo('email')
validarCampo('edad')
validarPasswordEnTiempoReal()
validarConfirmacionPassword()
}
</script>
Este enfoque de validaciones simples con JavaScript nativo proporciona un control total sobre el comportamiento del formulario sin depender de librerías externas. La combinación de validaciones en tiempo real, validaciones por campo y validaciones al envío crea una experiencia de usuario robusta y profesional, estableciendo las bases para formularios más complejos en aplicaciones Vue.js.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Vuejs
Documentación oficial de Vuejs
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Vuejs es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Vuejs
Explora más contenido relacionado con Vuejs y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender el funcionamiento y sintaxis básica de la directiva v-model en Vue.js.
- Aprender a manejar diferentes tipos de inputs (texto, número, checkbox, radio, select) con v-model.
- Implementar validaciones simples y en tiempo real en formularios Vue.
- Gestionar el envío de formularios con validación previa y feedback al usuario.
- Aplicar validaciones condicionales y dependientes entre campos del formulario.