v-model на компонентах
v-model работает не только с полями ввода, но и с вашими компонентами. Это удобно, когда компонент редактирует одно значение родителя: строку поиска, выбранный фильтр, текст новой задачи. Для этого есть макрос defineModel, доступный с Vue 3.4.
Компонент с defineModel
Создайте src/components/TodoInput.vue:
<!-- src/components/TodoInput.vue -->
<script setup>
const text = defineModel()
</script>
<template>
<input v-model="text" placeholder="Что нужно сделать?" class="todo-input">
</template>
<style scoped>
.todo-input {
padding: 8px;
width: 100%;
}
</style>defineModel() возвращает ref, связанный со значением родителя. Чтение даёт значение из родителя, а запись сообщает родителю новое значение.
Использование
<!-- src/App.vue -->
<script setup>
import { ref } from 'vue'
import TodoInput from './components/TodoInput.vue'
const newTodo = ref('')
</script>
<template>
<TodoInput v-model="newTodo" />
<p>Новая задача: {{ newTodo }}</p>
</template>Текст под полем повторяет ввод — значение из компонента попадает в newTodo родителя. Если родитель запишет в newTodo пустую строку, поле в TodoInput очистится.
Что происходит внутри
v-model на компоненте — сокращение для параметра и события:
<TodoInput
:modelValue="newTodo"
@update:modelValue="(value) => (newTodo = value)"
/>defineModel объявляет и параметр modelValue, и событие update:modelValue, поэтому писать defineProps и defineEmits вручную не нужно. Так же устроены компоненты в старом коде, написанном до Vue 3.4.
Несколько моделей
Модели можно дать имя и использовать несколько v-model на одном компоненте. Создайте src/components/TodoFilter.vue:
<!-- src/components/TodoFilter.vue -->
<script setup>
const filter = defineModel('filter', { default: 'all' })
const query = defineModel('query', { default: '' })
</script>
<template>
<input v-model="query" placeholder="Поиск">
<button @click="filter = 'all'">Все</button>
<button @click="filter = 'active'">Активные</button>
<button @click="filter = 'done'">Выполненные</button>
</template><!-- src/App.vue, фрагмент шаблона -->
<TodoFilter v-model:filter="filter" v-model:query="query" />
<p>Фильтр: {{ filter }}, поиск: {{ query }}</p>Кнопки меняют filter в родителе, а поле — query. Переменные filter и query объявлены в App.vue через ref.
Частая ошибка
Принять значение через defineProps и привязать к нему v-model внутри компонента:
<script setup>
const props = defineProps(['modelValue'])
</script>
<template>
<input v-model="props.modelValue">
</template>Так компонент пытается изменить параметр, который принадлежит родителю, и Vue выдаст предупреждение, а родитель изменения не получит. Используйте defineModel — он отправит изменение родителю через событие.