v-model w komponentach
Podstawowe użycie
v-model
może być używany w komponencie do implementacji dwukierunkowego wiązania danych.
Począwszy od Vue 3.4, zalecanym podejściem do osiągnięcia tego jest użycie makra defineModel()
:
vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Związany v-model rodzica to: {{ model }}</div>
<button @click="update">Zwiększ</button>
</template>
Rodzic może następnie powiązać wartość za pomocą v-model
:
template
<!-- Parent.vue -->
<Child v-model="countModel" />
Wartość zwracana przez defineModel()
jest referencją (ref). Może być ona odczytywana i modyfikowana jak każda inna referencja, z tą różnicą, że działa jako dwukierunkowe wiązanie między wartością rodzica a wartością lokalną:
- Jej
.value
jest zsynchronizowana z wartością powiązaną przez rodzica za pomocąv-model
; - Kiedy jest modyfikowana przez dziecko, powoduje również aktualizację wartości powiązanej w rodzicu.
Oznacza to, że możesz również powiązać tę referencję z natywnym elementem input za pomocą v-model
, co ułatwia opakowywanie natywnych elementów input przy zachowaniu takiego samego sposobu użycia v-model
:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
Pod maską
defineModel
jest makrem dla wygody. Kompilator rozwija je do następujących elementów:
- Właściwości o nazwie
modelValue
, z którą synchronizowana jest wartość lokalnej referencji; - Zdarzenia o nazwie
update:modelValue
, które jest emitowane, gdy wartość lokalnej referencji jest modyfikowana.
Tak wyglądałaby implementacja tego samego komponentu potomnego pokazanego powyżej przed wersją 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>
Następnie v-model="foo"
w komponencie rodzica zostanie skompilowany do:
template
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
Jak widać, jest to znacznie bardziej rozwlekłe. Jednak pomocne jest zrozumienie, co dzieje się pod maską.
Ponieważ defineModel
deklaruje właściwość (prop), możesz zadeklarować opcje bazowej właściwości, przekazując je do defineModel
:
js
// v-model jako wymagane
const model = defineModel({ required: true })
// zdefiniowana wartość podstawowa
const model = defineModel({ default: 0 })
WARNING
Jeśli masz wartość default
dla właściwości defineModel
i nie przekazujesz żadnej wartości dla tej właściwości z komponentu rodzica, może to spowodować desynchronizację między komponentami rodzica i potomka. W poniższym przykładzie, myRef
rodzica jest niezdefiniowane (undefined), ale model
potomka ma wartość 1:
js
// komponent podrzędny:
const model = defineModel({ default: 1 })
// komponent nadrzędny:
const myRef = ref()
html
<Child v-model="myRef"></Child>
Argumenty v-model
v-model
w komponencie może również przyjmować argument:
template
<MyComponent v-model:title="bookTitle" />
W komponencie potomnym możemy obsłużyć odpowiedni argument przekazując ciąg znaków do defineModel()
jako jego pierwszy argument:
vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
Jeśli potrzebne są również opcje właściwości (prop), powinny być one przekazane po nazwie modelu:
js
const title = defineModel('title', { required: true })
Użycie przed wersją 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>
Wiele wiązań v-model
Wykorzystując możliwość wskazania konkretnej właściwości (prop) i zdarzenia, którą poznaliśmy wcześniej w sekcji argumenty v-model
, możemy teraz utworzyć wiele wiązań v-model
w jednej instancji komponentu.
Każdy v-model
będzie synchronizował się z inną właściwością, bez potrzeby dodatkowych opcji w komponencie:
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>
Użycie przed wersją 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>
Obsługa modyfikatorów v-model
Podczas nauki o wiązaniach danych formularza poznaliśmy wbudowane modyfikatory dla v-model
- .trim
, .number
i .lazy
. W niektórych przypadkach możesz również chcieć, aby v-model
w twoim własnym komponencie wejściowym obsługiwał niestandardowe modyfikatory.
Stwórzmy przykładowy niestandardowy modyfikator capitalize
, który zmienia pierwszą literę ciągu znaków dostarczonego przez wiązanie v-model
na wielką:
template
<MyComponent v-model.capitalize="myText" />
Modyfikatory dodane do v-model
komponentu są dostępne w komponencie potomnym poprzez destrukturyzację wartości zwracanej przez defineModel()
w następujący sposób:
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
Aby warunkowo dostosować sposób odczytu/zapisu wartości w zależności od modyfikatorów, możemy przekazać opcje get
i set
do defineModel()
. Te dwie opcje otrzymują wartość podczas operacji get/set referencji modelu i powinny zwracać przekształconą wartość. Oto jak możemy wykorzystać opcję set
do zaimplementowania modyfikatora 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>
Użycie przed wersją 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>
Modyfikatory dla v-model
z argumentami
Oto kolejny przykład użycia modyfikatorów z wieloma v-model
z różnymi argumentami:
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>
Użycie przed wersją 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>