Componente v-model
Uso Básico
v-model puede usarse en un componente para implementar un enlace bidireccional.
A partir de Vue 3.4, el enfoque recomendado para lograr esto es usando la macro defineModel():
vue
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>El v-model del padre es: {{ model }}</div>
<button @click="update">Incrementar</button>
</template>El padre puede entonces enlazar un valor con v-model:
template
<Child v-model="countModel" />El valor devuelto por defineModel() es una ref. Se puede acceder y mutar como cualquier otra ref, excepto que actúa como un enlace bidireccional entre un valor padre y uno local:
- Su
.valuese sincroniza con el valor enlazado por elv-modeldel padre; - Cuando es mutado por el hijo, también provoca que el valor enlazado por el padre se actualice.
Esto significa que también puedes enlazar esta ref a un elemento de entrada nativo con v-model, lo que facilita envolver elementos de entrada nativos mientras proporcionas el mismo uso de v-model:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>Pruébalo en el parque de pruebas
Internamente
defineModel es una macro de conveniencia. El compilador la expande a lo siguiente:
- Una prop llamada
modelValue, con la que se sincroniza el valor de la ref local; - Un evento llamado
update:modelValue, que se emite cuando el valor de la ref local es mutado.
Así es como implementarías el mismo componente hijo mostrado anteriormente antes de la versión 3.4:
vue
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>Entonces, v-model="foo" en el componente padre se compilará a:
template
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>Como puedes ver, es un poco más verboso. Sin embargo, es útil entender lo que sucede internamente.
Dado que defineModel declara una prop, puedes declarar las opciones de la prop subyacente pasándolas a defineModel:
js
// haciendo que el v-model sea requerido
const model = defineModel({ required: true })
// proporcionando un valor por defecto
const model = defineModel({ default: 0 })WARNING
Si tienes un valor default para la prop de defineModel y no proporcionas ningún valor para esta prop desde el componente padre, puede causar una desincronización entre los componentes padre e hijo. En el ejemplo siguiente, la myRef del padre es undefined, pero el model del hijo es 1:
vue
<script setup>
const model = defineModel({ default: 1 })
</script>vue
<script setup>
const myRef = ref()
</script>
<template>
<Child v-model="myRef"></Child>
</template>Argumentos de v-model
v-model en un componente también puede aceptar un argumento:
template
<MyComponent v-model:title="bookTitle" />En el componente hijo, podemos soportar el argumento correspondiente pasando una cadena a defineModel() como su primer argumento:
vue
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>Si también se necesitan opciones de prop, deben pasarse después del nombre del model:
js
const title = defineModel('title', { required: true })Uso anterior a 3.4
vue
<script setup>
defineProps({
title: {
required: true
}
})
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>Múltiples Enlaces v-model
Aprovechando la capacidad de apuntar a una prop y evento particulares, como aprendimos antes con los argumentos de v-model, ahora podemos crear múltiples enlaces v-model en una sola instancia de componente.
Cada v-model se sincronizará con una prop diferente, sin necesidad de opciones adicionales en el componente:
template
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>Uso anterior a 3.4
vue
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>Manejo de los Modificadores de v-model
Cuando aprendimos sobre los enlaces de entrada de formulario, vimos que v-model tiene modificadores integrados: .trim, .number y .lazy. En algunos casos, también podrías querer que el v-model en tu componente de entrada personalizado admita modificadores personalizados.
Creemos un ejemplo de modificador personalizado, capitalize, que pone en mayúscula la primera letra de la cadena proporcionada por el enlace v-model:
template
<MyComponent v-model.capitalize="myText" />Los modificadores añadidos a un v-model de componente se pueden acceder en el componente hijo desestructurando el valor de retorno de defineModel() de esta manera:
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>Para ajustar condicionalmente cómo se debe leer/escribir el valor basándose en los modificadores, podemos pasar las opciones get y set a defineModel(). Estas dos opciones reciben el valor al obtener/establecer la ref del modelo y deben devolver un valor transformado. Así es como podemos usar la opción set para implementar el modificador capitalize:
vue
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>Uso anterior a 3.4
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="props.modelValue" @input="emitValue" />
</template>Modificadores para v-model con Argumentos
Aquí hay otro ejemplo de cómo usar modificadores con múltiples v-model con diferentes argumentos:
template
<UserName
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
/>vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')
console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>Uso anterior a 3.4
vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])
console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true }
</script>







