Vue
<script setup>: defineProps, defineEmits, defineModel
Компонент карточки товара должен принимать название и цену снаружи, сообщать родителю о клике по кнопке покупки и поддерживать v-model для поля количества. В Options API это три разных секции: props, emits, и ручная связка для v-model. В script setup всё это три строки с макросами: defineProps, defineEmits, defineModel. Они выглядят как обычные функции, но это компиляторные макросы - их не нужно импортировать, и работают они только на этапе сборки. Понимание этой детали снимает путаницу, почему их нельзя вызвать как обычный код.
- Карточка товара принимает title и price через defineProps и отдаёт событие add-to-cart через defineEmits
- Кастомный компонент поля ввода поддерживает v-model родителя через defineModel без ручной связки prop и события
- Модальное окно принимает флаг открытия через v-model и сообщает о закрытии событием наверх
- Компонент-обёртка над сторонней библиотекой через defineExpose открывает родителю метод focus или reset
- Типизированные props через дженерик defineProps дают автодополнение и проверку типов в IDE на этапе разработки
Предварительные знания
- Понимание формата SFC и script setup из урока vue-03
- Знакомство с ref из урока vue-06, потому что defineModel возвращает ref
- Понимание односторонней передачи данных от родителя к ребёнку через props
- Базовое знание событий и v-model из урока о шаблонах vue-04
defineProps и defineEmits
Компонент получает данные от родителя через props и сообщает родителю о событиях через emits. В script setup для этого есть два макроса. defineProps объявляет, какие входные данные компонент принимает, и возвращает объект для доступа к ним. defineEmits объявляет, какие события компонент может отправить, и возвращает функцию для их вызова. Это формализует контракт компонента: что входит и что выходит.
С TypeScript типы props и событий задаются дженериком, и это даёт проверку типов и автодополнение в IDE. Родитель передаёт props как атрибуты: title и price. При клике компонент вызывает emit с именем события add-to-cart, а родитель слушает его через @add-to-cart. Ключевое правило: props доступны только для чтения внутри ребёнка. Менять их напрямую нельзя - вместо этого ребёнок шлёт событие, и родитель сам решает, обновлять ли данные.
Односторонний поток данных вниз и события вверх делают поведение предсказуемым. Данные текут от родителя к детям через props, а изменения возвращаются наверх через события. Чтобы понять, откуда взялось значение, достаточно посмотреть на родителя, а не искать, кто из детей его поменял.
Почему ребёнок не должен менять полученный prop напрямую, а вместо этого шлёт событие наверх?
defineModel для v-model
Директива v-model на компоненте это двунаправленная привязка: родитель передаёт значение вниз и автоматически получает обновления наверх. Раньше для поддержки v-model в своём компоненте приходилось вручную объявлять prop modelValue и событие update modelValue, связывая их. Макрос defineModel убирает эту рутину: он создаёт двунаправленную привязку одной строкой и возвращает ref.
Возвращённый defineModel объект это ref. Чтение model даёт текущее значение, запись через model.value (или прямое присваивание в шаблоне) уведомляет родителя об изменении. Внутри компонента это выглядит как обычный локальный ref, но под капотом он синхронизирован с v-model родителя. В шаблоне выше при вводе текста присваивание в model сразу отражается в переменной name родителя.
Один компонент может иметь несколько v-model через именованные модели: defineModel('firstName') и defineModel('lastName'). Родитель привязывает их как v-model:first-name и v-model:last-name. Это удобно для компонентов вроде формы адреса, где наружу выходит несколько связанных значений.
Что возвращает макрос defineModel и как с ним работать?
Макросы компилятора и defineExpose
Важная деталь обо всех этих функциях: defineProps, defineEmits, defineModel и defineExpose это не обычные функции, а компиляторные макросы. Их не нужно импортировать из vue, и они существуют только внутри script setup. Компилятор Vue распознаёт их на этапе сборки и превращает в соответствующий код. Поэтому их нельзя вызвать условно, в цикле или вынести в отдельную функцию - они обрабатываются статически, а не выполняются в рантайме.
По умолчанию компонент на script setup закрыт снаружи: родитель, получивший ссылку на экземпляр компонента, не видит его внутренних переменных и методов. Это сознательная инкапсуляция. Макрос defineExpose открывает наружу только то, что явно перечислено. Через него родитель получает доступ к конкретному методу или значению ребёнка, например, чтобы вызвать focus у поля ввода или reset у формы.
defineExpose стоит применять экономно. Чем больше внутренностей компонента открыто наружу, тем сильнее родитель завязан на его реализацию, и тем труднее менять компонент без поломки. Предпочтительный способ общения это props вниз и события вверх. defineExpose оправдан для императивных действий вроде focus или reset, которые неудобно выразить через декларативный поток данных.
Почему defineProps и другие define-функции называют компиляторными макросами, а не обычными функциями?
Связь с другими темами
Этот урок собирает базовый инструментарий общения компонентов:
- Single File Component — Макросы живут внутри блока script setup, формат которого разобран ранее
- ref и reactive — defineModel возвращает ref, и работа с ним опирается на понимание реактивных контейнеров
Итог
- defineProps, defineEmits, defineModel и defineExpose это компиляторные макросы: они не импортируются и обрабатываются на этапе сборки, а не выполняются как обычные функции в рантайме
- defineProps объявляет типизированные входные данные компонента, которые родитель передаёт через атрибуты, props доступны только для чтения внутри ребёнка
- defineEmits объявляет события, которые компонент шлёт наверх родителю, заменяя прямую мутацию props на явное уведомление
- defineModel создаёт двунаправленную привязку для v-model, возвращая ref, чтение и запись которого синхронизированы с родителем без ручной пары prop и событие
- defineExpose контролирует публичный интерфейс: по умолчанию script setup закрыт снаружи, и только перечисленное в defineExpose доступно родителю через ссылку на компонент
Связанные уроки
- vue-03-sfc-components — Макросы работают только внутри script setup, формат которого разобран в третьем уроке
- vue-06-ref-reactive — defineModel возвращает ref, а props читаются реактивно, поэтому понимание ref здесь применяется напрямую