Директива v-model
Пример использования
v-model можно использовать в компоненте для реализации двусторонней привязки.
Начиная с Vue 3.4, для достижения этой цели рекомендуется использовать макрос defineModel():
vue
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>v-model, связанная с родительским компонентом: {{ model }}</div>
<button @click="update">Увеличить</button>
</template>Родитель может связать значение с v-model:
template
<Child v-model="countModel" />Значение, возвращаемое функцией defineModel(), является ссылкой. К нему можно обращаться и изменять, как к любому другому ref, за исключением того, что он действует как двусторонняя привязка между родительским значением и локальным:
- Его
.valueсинхронизируется со значением, связанным с родительскойv-model; - Когда оно изменяется дочерним элементом, это приводит к обновлению родительского связанного значения.
Это означает, что вы также можете привязать эту ссылку к нативному элементу ввода с помощью v-model, что позволяет легко обернуть нативные элементы ввода, обеспечивая при этом использование v-model:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>Под капотом
defineModel — это удобный макрос. Компилятор расширит его до следующего вида:
- Свойство с именем
modelValue, с которым синхронизируется значение локальной ссылки; - Событие с именем
update:modelValue, которое испускается при изменении значения локальной ссылки.
Вот как можно реализовать тот же дочерний компонент, показанный выше, до версии 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>Тогда v-model="foo" в родительском компоненте будет скомпилировано в:
template
<Child
:modelValue="foo"
@update:modelValue="($event) => (foo = $event)"
/>Как видите, он более многословен. Однако полезно понимать, что происходит под капотом.
Поскольку defineModel объявляет prop, вы можете объявить опции базового prop, передав его в defineModel:
js
// делаем v-model обязательным
const model = defineModel({ required: true })
// предоставляем значение по умолчанию
const model = defineModel({ default: 0 })Предупреждение
Если у вас есть значение default для свойства defineModel и вы не предоставляете никакого значения для этого свойства из родительского компонента, это может привести к рассинхронизации между родительским и дочерним компонентами. В примере ниже родительский myRef не определен, а дочерний model равен 1:
vue
<script setup>
const model = defineModel({ default: 1 })
</script>vue
<script setup>
const myRef = ref()
</script>
<template>
<Child v-model="myRef"></Child>
</template>Аргументы v-model
v-model на компоненте также может принимать аргумент:
template
<MyComponent v-model:title="bookTitle" />В дочернем компоненте мы можем поддержать соответствующий аргумент, передав строку в defineModel() в качестве его первого аргумента:
vue
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>Если также необходимы параметры пропсов, их следует передать после имени модели:
js
const title = defineModel('title', { required: true })Использование до 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>Несколько привязок v-model
Используя возможность нацеливания на определённый параметр и событие, как мы уже узнали ранее с помощью аргументов v-model, теперь мы можем создать несколько привязок v-model на одном экземпляре компонента.
Каждая v-model будет синхронизироваться с отдельным параметром, без необходимости использования дополнительных опций в компоненте:
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>Использование до 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>Обработка модификаторов v-model
Когда мы изучали привязки ввода формы, мы увидели, что у v-model есть встроенные модификаторы — .trim, .number и .lazy. В некоторых случаях вы также можете захотеть, чтобы v-model вашего компонента ввода поддерживала пользовательские модификаторы.
Давайте создадим пример пользовательского модификатора capitalize, который выделит первую букву строки, предоставленной привязкой v-model:
template
<MyComponent v-model.capitalize="myText" />Модификаторы, добавленные в компонент v-model, могут быть доступны в дочернем компоненте путём деструктуризации возвращаемого значения defineModel() следующим образом:
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>Чтобы условно настроить чтение/запись значения на основе модификаторов, мы можем передать get и set опции в defineModel(). Эти две опции получают значение при get/set модели ref и должны возвращать преобразованное значение. Вот как мы можем использовать опцию set для реализации модификатора 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>Использование до 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>Модификаторы для v-model с аргументами
Вот ещё один пример использования модификаторов с несколькими v-model с разными аргументами:
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>Использование до 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>