Componente v-model
Uso básico
v-model
se puede usar en un componente para implementar una vinculación bidireccional.
A partir de Vue 3.4, el método recomendado para lograr esto es usando la macro defineModel()
:
vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>El `v-model` vinculado al padre es: {{ model }}</div>
</template>
El padre puede entonces enlazar un valor con v-model
:
template
<!-- Parent.vue -->
<Child v-model="count" />
El valor devuelto por defineModel()
es una referencia (ref
). Puede ser accedido y mutado como cualquier otra referencia, excepto que actúa como un enlace bidireccional entre un valor del padre y uno local:
- Su
.value
está sincronizado con el valor enlazado por elv-model
del padre; - Cuando es mutado por el hijo, también hace que el valor enlazado del padre se actualice.
Esto significa que también puedes enlazar esta referencia a un elemento de entrada nativo con v-model
, lo que facilita envolver elementos de entrada nativos mientras se proporciona el mismo uso de v-model
:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
Pruébalo en la Zona de Práctica
Detrás de escena
defineModel
es un macro de conveniencia. El compilador lo expande a lo siguiente:
- Una propiedad llamada
modelValue
, con la cual se sincroniza el valor del ref local; - Un evento llamado
update:modelValue
, que se emite cuando se muta el valor del ref local.
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>
Como puedes ver, es un poco más largo. Sin embargo, es útil entender qué está sucediendo internamente.
Dado que defineModel
declara una prop, puedes declarar las opciones de la prop asociadas pasándola a defineModel
:
js
// Haciendo que el v-model sea obligatorio
const model = defineModel({ required: true })
// Proporcionando un valor predeterminado
const model = defineModel({ default: 0 })
WARNING
Si tienes un valor default
para la prop defineModel
y no proporcionas ningún valor para esta prop desde el componente padre, puede causar una desincronización entre el componente padre y el componente hijo. En el ejemplo siguiente, el myRef
del padre es indefinido, pero el model
del hijo es 1:
js
// componente hijo:
const model = defineModel({ default: 1 })
// componente padre:
const myRef = ref()
html
<Child v-model="myRef"></Child>
Argumentos de v-model
vue
<MyComponent v-model:title="bookTitle" />
En un componente hijo, podemos admitir el argumento correspondiente pasando una cadena a defineModel()
como su primer argumento:
vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
Pruébalo en la Zona de Práctica
Si también se necesitan opciones de prop, deben pasarse después del nombre del model:
js
const title = defineModel('title', { required: true })
Uso previo a 3.4
vue
<!-- MyComponent.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>
Multiples vinculaciones a v-model
vue
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
Al aprovechar la capacidad de apuntar a una prop y evento específicos como aprendimos anteriormente 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 la necesidad de opciones adicionales en el componente:
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>
Pruébalo en la Zona de Práctica
Uso previo 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 modificadores de v-model
Cuando estábamos aprendiendo sobre las vinculaciones de entrada de formularios, vimos que v-model
tiene modificadores integrados: .trim
, .number
y .lazy
. En algunos casos, es posible que también desees que el v-model
en tu componente de entrada personalizado admita modificadores personalizados.
Creemos un ejemplo de modificador personalizado, capitalize
, que capitalice la primera letra de la cadena de texto proporcionada por la vinculación de v-model
:
template
<MyComponent v-model.capitalize="myText" />
Los modificadores agregados a un componente v-model
pueden ser accedidos en el componente hijo mediante la destructuración del 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 debe ser leído o escrito el valor en función de los modificadores, podemos pasar opciones get
y set
a defineModel()
. Estas dos opciones reciben el valor en la obtención / establecimiento de la ref del model y deben devolver un valor transformado. Así es como podemos utilizar 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>
Pruébalo en la Zona de Práctica
Uso previo 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="modelValue" @input="emitValue" />
</template>
Modificadores para v-model
con argumentos
Aquí tienes 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 previo 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>