Директива v-model
Пример использования
v-model
можно использовать в компоненте для реализации двусторонней привязки.
Начиная с Vue 3.4, для достижения этой цели рекомендуется использовать макрос defineModel()
:
vue
<!-- Child.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
<!-- Parent.vue -->
<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
<!-- 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>
Тогда v-model="foo"
в родительском компоненте будет скомпилировано в:
template
<!-- Parent.vue -->
<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:
Дочерний компонент:
js
const model = defineModel({ default: 1 })
Родительский компонент:
js
const myRef = ref()
html
<Child v-model="myRef"></Child>
Аргументы v-model
v-model
на компоненте также может принимать аргумент:
template
<MyComponent v-model:title="bookTitle" />
В дочернем компоненте мы можем поддержать соответствующий аргумент, передав строку в defineModel()
в качестве его первого аргумента:
vue
<!-- MyComponent.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
<!-- 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-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>