Однофайловые компоненты

Что это такое

Однофайловый компонент — это один файл .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, которые однофайловый компонент делает конкретными.