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
<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
<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
.valuejest 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
<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
<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:
vue
<script setup> const model = defineModel({ default: 1 }) </script>vue
<script setup> const myRef = ref() </script> <template> <Child v-model="myRef"></Child> </template>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
<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
<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="props.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>