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
. К ней можно получить доступ и изменить так же, как и любую другую ref
, за исключением того, что она действует как двустороннее связывание между родительским значением и локальным значением:
- Значение
.value
синхронизировано со значением, связанным с родительскимv-model
; - Когда оно изменяется дочерним элементом, это приводит к обновлению значения, связанного с родителем.
Это означает, что вы также можете связать этот ref
к нативному полю ввода с помощью v-model
, что делает оборачивание элементов ввода простым и обеспечивает аналогичное использование v-model
:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
Под капотом
defineModel
- это удобный макрос. Компилятор раскрывает его в следующее:
- Свойство с именем
modelValue
, значение которого синхронизировано со значением локальнойref
; - Событие с именем
update:modelValue
, которое возникает при изменении значения локальнойref
.
Вот как вы бы реализовали тот же дочерний компонент, который был показан выше до версии 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
объявляет входные параметры, вы можете также объявить входные параметры основного свойства, передав его в defineModel
:
js
// делает v-model обязательным
const model = defineModel({ required: true })
// установка значения по умолчанию
const model = defineModel({ default: 0 })
Предупреждение
Если у вас есть значение default
для свойства defineModel
, и вы не предоставляете никакого значения для этого свойства из родительского компонента, это может привести к рассинхронизации между родительским и дочерним компонентами. В приведенном ниже примере родительский myRef
не определен, а дочерний model
равен 1:
Child component:
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()
. Эти две опции получают значение при получении / установке 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>