v-model du composant
Utilisation de base
v-model
peut être utilisé sur un composant pour implémenter une liaison à double sens.
À partir de Vue 3.4, l'approche recommandée consiste à utiliser la macro defineModel()
:
vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Le v-model lié au parent est : {{ model }}</div>
<button @click="update">Incrément</button>
</template>
Le parent peut alors lier une valeur avec v-model
:
template
<!-- Parent.vue -->
<Child v-model="countModel" />
La valeur retournée par defineModel()
est une ref. On peut y accéder et la modifier comme n'importe quel autre ref, sauf qu'elle agit comme une liaison bidirectionnelle entre une valeur parent et une valeur locale :
- Sa
.value
est synchronisée avec la valeur liée auv-model
parent ; - Lorsqu'elle est modifiée par l'enfant, la valeur liée au parent est également mise à jour.
Cela signifie que vous pouvez également lier cette ref à un élément d'entrée natif avec v-model
, ce qui permet d'envelopper les éléments d'entrée natifs tout en fournissant la même utilisation de v-model
:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
Sous le capot
defineModel
est une macro de commodité. Le compilateur l'étend de la manière suivante :
- Une propriété nommée
modelValue
, avec laquelle la valeur de la ref locale est synchronisée ; - Un événement nommé
update:modelValue
, qui est émis lorsque la valeur de la ref locale est modifiée.
C'est ainsi que l'on mettrait en œuvre le même composant enfant que celui présenté ci-dessus avant la version 3.4 :
vue
<!-- Child.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>
Ensuite, v-model="foo"
dans le composant parent sera compilé en :
template
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
Comme vous pouvez le voir, c'est un peu plus verbeux. Cependant, il est utile de comprendre ce qui se passe sous le capot.
Puisque defineModel
déclare une prop, vous pouvez donc déclarer les options de la prop sous-jacente en la passant à defineModel
:
js
// rendre v-model obligatoire
const model = defineModel({ required: true })
// fournir une valeur par défaut
const model = defineModel({ default: 0 })
WARNING
Si vous avez une valeur default
pour la propriété defineModel
et que vous ne fournissez aucune valeur pour cette propriété à partir du composant parent, cela peut provoquer une désynchronisation entre les composants parent et enfant. Dans l'exemple ci-dessous, le composant parent myRef
est undefined, mais le composant enfant model
vaut 1 :
Composant enfant :
js
const model = defineModel({ default: 1 })
Composant parent :
const myRef = ref()
html
<Child v-model="myRef"></Child>
Les arguments de v-model
v-model
sur un composant peut également accepter un argument :
template
<MyComponent v-model:title="bookTitle" />
Dans le composant enfant, nous pouvons prendre en charge l'argument correspondant en passant une chaîne à defineModel()
comme premier argument :
vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
Si des options de prop sont également nécessaires, elles doivent être transmises après le nom du modèle :
js
const title = defineModel('title', { required: true })
Utilisation avant la 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>
Liaisons multiple avec v-model
En tirant parti de la possibilité de cibler une prop et un événement en particulier, comme nous l'avons appris précédemment avec les arguments de v-model
, nous pouvons désormais créer plusieurs liaisons v-model sur une seule instance de composant.
Chaque v-model se synchronisera avec une prop différente, sans avoir besoin d'options supplémentaires dans le composant :
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>
Utilisation avant la 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>
Gestion des modificateurs de v-model
Lorsque nous avons appris les liaisons d'entrée de formulaire, nous avons vu que v-model
avait des modificateurs natifs - .trim
, .number
et .lazy
. Dans certains cas, vous pouvez également souhaiter que le v-model
de votre composant d'entrée personnalisé prenne en charge les modificateurs personnalisés.
Créons un exemple de modificateur personnalisé, capitalize
, qui met en majuscule la première lettre de la chaîne de caractères fournie par la liaison v-model
:
template
<MyComponent v-model.capitalize="myText" />
Les modificateurs ajoutés à un composant v-model
sont accessibles dans le composant enfant en déstructurant la valeur de retour de defineModel()
comme suit :
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
Pour ajuster conditionnellement la façon dont la valeur doit être lue / écrite en fonction des modificateurs, nous pouvons passer les options get
et set
à defineModel()
. Ces deux options reçoivent la valeur sur get / set du modèle ref et doivent retourner une valeur transformée. C'est ainsi que nous pouvons utiliser l'option set
pour implémenter le modificateur 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>
Utilisation avant la 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>
Modificateurs pour v-model
avec arguments
Voici un autre exemple d'utilisation de modificateurs avec plusieurs v-model
ayant des arguments différents :
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>
Utilisation avant la 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>