Урок 3 · Vue · 20 минут
Компоненты Vue: собираем интерфейс из частей
Компонент — самостоятельный фрагмент интерфейса со своей разметкой и логикой. Карточку товара или пункт меню пишут один раз, а используют столько раз, сколько нужно.
Данные через props
<script setup>
defineProps({ title: String })
</script>
<template><h2>{{ title }}</h2></template>Родитель передаёт компоненту данные: <LessonCard title="Vue" />. Компонент не должен менять prop: отправляй событие родителю, если требуется обновление.
Когда выносить компонент
Выноси блок, если он повторяется, имеет собственную задачу или мешает читать большую страницу. Не нужно дробить каждую строку: у компонента должна быть понятная роль.
Ошибки
- Менять prop внутри дочернего компонента.
- Не давать компоненту понятное имя из двух слов.
- Передавать слишком много несвязанных props.
Props приходят сверху, события уходят наверх
Дочерний компонент получает данные через defineProps и не меняет их. О клике он сообщает через emit. Родитель решает, удалять элемент или нет, потому что список живёт у родителя. Так одна карточка не обязана знать, откуда взялся весь каталог.
Имена props в шаблоне обычно пишут через дефис, в скрипте — camelCase. lesson-id в разметке станет lessonId в объекте props. Это легко перепутать в первый вечер.
<script setup>
defineProps({
title: { type: String, required: true },
minutes: { type: Number, default: 10 }
});
const emit = defineEmits(["remove"]);
</script>
<template>
<article>
<h2>{{ title }}</h2>
<button type="button" @click="emit('remove')">Убрать</button>
</article>
</template>Слот оставляет содержимое родителю
Если каждая карточка внутри разная, не плоди десяток флагов. Оставь в карточке рамку и заголовок, а тело передай слотом. Родитель напишет между тегами компонента свою разметку. Именованные слоты нужны, когда таких мест несколько: шапка, действие, подвал.
Как делить без крошек
- Компонент можно объяснить фразой и дать ему свои данные на вход.
- Он не тянет сам список с сервера, если тем же списком пользуются соседи.
- Стили карточки не зависят от того, что она обязательно лежит в конкретной секции.
- Один компонент не читает внутренности другого через селектор.
Практика
Сделай компонент карточки урока с props title и level.
Решение
defineProps({ title: String, level: String })Как компоненты обмениваются данными
Родитель передаёт данные вниз через props, а ребёнок сообщает о действиях через emit. Такой однонаправленный поток облегчает поиск ошибок. Не изменяй prop внутри дочернего компонента: если нужно редактирование, создай локальное состояние или отправь событие родителю.
Слоты подходят для разметки, которую родитель должен вставить внутрь оболочки компонента. Это удобнее десятка props вроде headerText и footerText. Имя компонента должно отражать роль — LessonCard или BaseButton. Слишком универсальный компонент с множеством режимов сложнее поддерживать, чем несколько небольших и очевидных.
Разберём рабочий пример
<script setup>
defineProps({ title: String })
const emit = defineEmits(["open"])
</script>
<template>
<article>
<h2>{{ title }}</h2>
<slot />
<button @click="emit('open')">Открыть</button>
</article>
</template>Не копируй пример вслепую: измени данные, убери одну строку и посмотри, как меняется результат. Такой эксперимент быстрее показывает роль каждой части кода, чем простое чтение.
Проверка перед использованием
- Props не изменяются внутри ребёнка.
- События описывают действие пользователя.
- Слоты используются для вложенной разметки.
- Компонент не перегружен несвязанными режимами.