Binding přes v-model
Základní použití
Direkrivu v-model
lze použít pro implementaci obousměrného (two-way) bindingu.
Od verze Vue 3.4 je doporučený postup použití makra defineModel()
:
vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Hodnota v-model z rodiče je: {{ model }}</div>
<button @click="update">Přidat</button>
</template>
Komponenta rodiče pak může provést binding hodnoty pomocí v-model
:
template
<!-- Parent.vue -->
<Child v-model="countModel" />
Hodnota vrácená z defineModel()
je ref. Může být přistupována a měněna jako jakýkoli jiný ref, kromě toho, že slouží jako obousměrný binding mezi hodnotou z rodiče a tou lokální:
- Jeho
.value
je synchronizována s hodnotou navázanou pomocív-model
v komponentě rodiče; - Když je změněna komponentou potomka, způsobí automaticky aktualizaci hodnoty i v komponentě rodiče.
To znamená, že můžete také provést binding tohoto refu na nativní input element pomocí v-model
, čímž se dá vstupní pole jednoduše a přímočaře obalit:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
Pod pokličkou
defineModel
je makro pro usnadnění zápisu. Překladač jej rozšíří na:
- vlastnost (prop)
modelValue
, se kterou je synchronizována lokální hodnota ref; - událost (event)
update:modelValue
, která je emitována při změně lokální hodnoty ref.
Takto se implementovala komponenta potomka se stejnou funkcionalitou před verzí 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>
Poté se v-model="foo"
v komponentě rodiče zkompiluje jako:
template
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
Jak můžete vidět, je to trochu zbytečně složité. Nicméně je dobré vědět, jak to ve skutečnosti funguje.
Protože defineModel
deklaruje vlastnost (prop), mužete následně upravovat její možnosti (options) předáním do defineModel
:
js
// v-model bude povinná hodnota
const model = defineModel({ required: true })
// poskytnutí výchozí hodnoty
const model = defineModel({ default: 0 })
VAROVÁNÍ
Pokud pro vlastnost defineModel
použijete default
a neposkytnete v komponentě rodiče žádnou hodnotu, může to způsobit de-synchronizaci mezi rodičem a potomkem. V příkladu níže je myRef
v rodiči undefined
, zatímco model
v potomkovi je 1
:
Komponenta potomka:
js
const model = defineModel({ default: 1 })
Komponenta rodiče:
js
const myRef = ref()
html
<Child v-model="myRef"></Child>
v-model
parametry
v-model
na komponentě může také přijmout parametr:
template
<MyComponent v-model:title="bookTitle" />
V komponentě potomka můžeme takový parametr podporovat předáním řetězce do defineModel()
vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
Pokud je potřeba předat i nastavení vlastnosti (prop options), je možné jej předat jako druhý parametr po názvu:
js
const title = defineModel('title', { required: true })
Použití před verzí 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>
Vícenásobný binding přes v-model
Využitím možnosti zaměřit se na konkrétní vlastnost a událost, jak jsme se to naučili dříve pomocí v-model
parametrů, můžeme nyní na jedné instanci komponenty vytvořit více v-model
vazeb.
Každý v-model
se bude synchronizovat s jinou vlastností bez nutnosti speciální konfigurace uvnitř komponenty:
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>
Použití před verzí 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>
Obsluha v-model
modifikátorů
Když jsme se učili o bindingu dat z formuláře, viděli jsme, že v-model
má vestavěné modifikátory - .trim
, .number
a .lazy
. V některých případech můžete chtít, aby v-model
na vaší input komponentě podporoval i vlastní (custom) modifikátory.
Pojďme vytvořit příklad vlastního modifikátoru capitalize
, který bude psát první znak řetězce zadaného přes v-model
binding velkými písmeny:
template
<MyComponent v-model.capitalize="myText" />
Modifikátory přidané na v-model
komponenty mohou být získány v komponentně potomka pomocí destrukturování návratové hodnoty defineModel()
takto:
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
Pro podmíněné nastavení jak by měla být hodnota na základě modifikátorů čtena / zapisována, můžeme do defineModel()
předat možnosti get
a set
. Tyto dvě nastavení získají hodnotu při čtení / zápisu příslušné ref hodnoty a měly by vrátit transformovanou hodnotu. Takto můžeme použít set
pro implementaci modifikátoru 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>
Použití před verzí 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>
Modifikátory pro v-model
s parametry
Zde je další příklad použití modifikátorů na více v-model
s různými parametry:
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>
Použití před verzí 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>