Componente v-model
v-model
se puede utilizar en un componente para implementar un enlace bidireccional.
Primero revisemos cómo se utiliza v-model
en un elemento nativo:
template
<input v-model="searchText" />
En el fondo, el compilador de plantillas expande v-model
al equivalente más descriptivo para nosotros. Así que el código anterior hace lo mismo que lo siguiente:
template
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
Cuando se utiliza en un componente, v-model
se convierte en esto:
template
<CustomInput
:model-value="searchText"
@update:model-value="newValue => searchText = newValue"
/>
Para que esto funcione realmente, el componente <CustomInput>
debe hacer dos cosas:
- Vincular el atributo
value
de un elemento nativo<input>
a la propsmodelValue
- Cuando se activa un evento nativo
input
, emite un evento personalizadoupdate:modelValue
con el nuevo valor
Aquí lo tienes en acción:
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Ahora v-model
debería funcionar perfectamente con este componente:
template
<CustomInput v-model="searchText" />
Otra forma de implementar v-model
dentro de este componente es utilizar una propiedad computed
editable con un getter y un setter. El método get
debe retornar la propiedad modelValue
y el método set
debe emitir el evento correspondiente:
vue
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
Argumentos de v-model
Por defecto, v-model
en un componente utiliza modelValue
como prop y update:modelValue
como evento. Podemos modificar estos nombres pasando un argumento a v-model
:
template
<MyComponent v-model:title="bookTitle" />
En este caso, el componente hijo debería esperar una props title
y emitir un evento update:title
para actualizar el valor del padre:
vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
Múltiples Vinculaciones v-model
Aprovechando la capacidad de apuntar a una prop y a un evento en particular como aprendimos antes en argumentos de v-model
, ahora podemos crear múltiples vinculaciones v-model
en una sola instancia del 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>
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 formularios, vimos que v-model
tiene modificadores integrados: .trim
, .number
y .lazy
. En algunos casos, es posible que también quieras que el v-model
de tu componente de entrada personalizado soporte modificadores personalizados.
Vamos a crear un ejemplo de modificador personalizado, capitalize
, que pone en mayúsculas la primera letra de la cadena proporcionada por el enlace v-model
:
template
<MyComponent v-model.capitalize="myText" />
Los modificadores añadidos a un componente v-model
serán proporcionados al componente a través de la prop modelModifiers
. En el siguiente ejemplo, hemos creado un componente que contiene una prop modelModifiers
que por defecto es un objeto vacío:
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Observa que la prop del componente modelModifiers
contiene capitalize
y su valor es true
debido a que ha sido configurado en el v-model
vinculando v-model.capitalize="myText"
.
Ahora que tenemos nuestra prop configurada, podemos comprobar las claves del objeto modelModifiers
y escribir un manejador para cambiar el valor emitido. En el código siguiente pondremos la cadena en mayúsculas siempre que el elemento <input />
dispare un evento input
.
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="modelValue" @input="emitValue" />
</template>
Modificadores para v-model
con argumentos
Para las vinculaciones v-model
con argumento y modificadores, el nombre de la prop generada será arg + "Modifiers"
. Por ejemplo:
template
<MyComponent v-model:title.capitalize="myText">
Las declaraciones correspondientes deberían ser:
js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }
Aquí hay otro ejemplo de uso de 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 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>