Однофайловые компоненты
Что это такое
Однофайловый компонент — это один файл .vue с блоком <script>, блоком <template> и необязательными блоками <style>. На этапе сборки сборочная цепочка компилирует его в обычный компонент. В этом и визитная карточка Vue: логика, разметка и стили — три вещи, которые меняются вместе, — живут в одном файле, а стили можно ограничить элементами этого файла.
Почему это важно
В совместном размещении — весь аргумент: когда поведение, разметка и стили компонента лежат в одном файле, правка означает открыть одно место, а удаление — удалить один файл. Альтернатива, отдельные каталоги шаблонов, скриптов и таблиц стилей, которые обязаны оставаться согласованными, обходится дороже: она стоит времени на навигацию при каждом изменении и оставляет мёртвые фрагменты, когда компонент убирают. Ограниченные стили добавляют вторую выгоду: имя класса можно переиспользовать в разных компонентах без соглашения об именовании, ведь компилятор переписывает его во что-то уникальное.
Ключевые идеи
Контракт компонента: props вниз, события вверх
Данные родитель передаёт через props, а изменения слушает через emits. Свои props ребёнок не изменяет никогда: когда ему нужно что-то поменять, он испускает событие, а решает родитель. Дерево компонентов делает предсказуемым именно это одно направление — та же дисциплина, которую каждый фреймворк навязывает по-своему.
<script setup>
const props = defineProps({
label: { type: String, required: true },
disabled: { type: Boolean, default: false },
});
const emit = defineEmits(["submit"]);
</script>
<template>
<button :disabled="disabled" @click="emit('submit')">{{ label }}</button>
</template>
Props объявляются и проверяются
Если объявить тип, обязательность и значение по умолчанию для каждого prop, контракт компонента начинает проверять фреймворк во время разработки и документировать для всякого, кто откроет файл. А prop, который требуется шаблоном, но не объявлен, обернётся сюрпризом во время выполнения.
Слоты — так внутрь передаётся структура
Место, которое заполняет родитель, — слот; так компонент принимает разметку, не получая её строкой. Компонент-раскладка с именованными слотами определяет несколько областей — шапку, тело, область действий, — оставаясь безразличным к тому, что в них попадёт.
<!-- Card.vue -->
<template>
<section class="card">
<header><slot name="header" /></header>
<slot />
</section>
</template>
Ограниченные стили изолируют без соглашения
scoped у блока <style> переписывает его селекторы так, что совпадают они только с элементами этого компонента. Соглашение о префиксах становится ненужным, однако ограниченное правило не может стилизовать внутренности дочернего компонента — и это скорее свойство: поверхность стилизации ограничена собственными элементами компонента и классами, которые он выставляет.
Для всего нетривиального предпочитайте composition API
Логику сверху вниз, без this, объявляет синтаксис <script setup>, поэтому функции и импорты читаются по порядку и переиспользуются между компонентами как composables. Старый options API всё ещё работает и часто встречается в существующем коде, зато новым компонентам идёт на пользу более плоская форма.
Практика
Извлеките переиспользуемый компонент
Найдите блок разметки, продублированный на двух экранах, и вынесите его в однофайловый компонент: props — для данных, слот — для изменяемой части.
Готово, когда: оба экрана используют компонент, компонент ничего не знает ни об одном из них, а продублированная разметка существует один раз.
Ограничьте стили компонента и докажите это
Дайте компоненту имя класса, которое используется и в другом месте страницы, добавьте scoped к блоку стилей и загляните в отрисованное имя класса через браузер.
Готово, когда: класс переписан в уникальную форму, другой элемент на странице не затронут и вы можете сказать, до чего область видимости не доходит.
Частые ошибки
- Изменение prop внутри ребёнка — данные родителя меняются без его ведома, и двое расходятся; исправить: испустите событие и позвольте родителю обновить значение.
- Объявление props без типов или значений по умолчанию — контракт невидим, и отсутствующее значение падает во время рендера; исправить: объявляйте тип, required и default для каждого prop.
- Передача разметки строкой через prop вместо слота — вызывающий не может вкладывать компоненты, а экранирование становится опасностью; исправить: используйте слот.
- Предположение, что ограниченный стиль достаёт до дочернего компонента — правило молча не применяется; исправить: выставите значение как пользовательское свойство или стилизуйте публичный класс ребёнка.
- Обращение к options API в новом коде по привычке —
thisразбрасывает логику, и composables нельзя переиспользовать; исправить: используйте<script setup>для новых компонентов.
Что читать дальше
- Реактивность и шаблоны (02-frontend/22-vuejs/01-reactivity-and-templates) — правила шаблона и реактивности, на которых построен этот формат файла.
- Управление состоянием (02-frontend/23-state-management) — где локального состояния компонента перестаёт хватать.
- UI-компоненты (02-frontend/12-ui-components) — решения о дизайне API, которые однофайловый компонент делает конкретными.