| Vue: концепция компонентов |
|
| Добавил(а) microsin | |||||||||||||||||||||||||
|
Концепция компонентов фреймворка Vue [1] — основополагающая идея о том, что пользовательский интерфейс (UI) можно представить в виде дерева небольших, переиспользуемых, самостоятельных блоков, каждый из которых отвечает за свою часть логики и отображения. Вместо одного огромного файла вы создаёте приложение из компонентов, как собираете сложную структуру из LEGO. Основная модель компонента — это однофайловый компонент (`.vue` файл), который инкапсулирует три части: шаблон (HTML), логику (JavaScript/TypeScript) и стили (CSS). Некую аналогию компонента можно произвести с классом языка C++ или модулем/структурой языка C, в которую встроены переменные и указатели на функции. [Ключевые принципы компонентов Vue]
[Анатомия однофайлового компонента (SFC - Single File Component)] Например, файл MyComponent.vue выглядит так: < !-- 1. Шаблон (HTML + директивы Vue) --> < template> < div class="my-component"> < h1>{{ title }}< /h1> < button @click="handleClick">Нажми меня< /button> < /div> < /template> [Как компоненты общаются: Props и Events] Связь "Родитель ↔ Ребёнок" - основа архитектуры Vue. < !-- РОДИТЕЛЬСКИЙ КОМПОНЕНТ --> < template> < !-- 1. Передаём данные "сверху вниз" через props --> < ChildComponent :message="parentMessage" /> < !-- 2. Слушаем события "снизу вверх" --> < ChildComponent @child-clicked="handleChildClick" /> < /template> [Дополнительные возможности компонентов] Слоты (Slots) — позволяют родителю передавать дочернему компоненту произвольное содержимое (разметку, другие компоненты), делая его более гибким. Асинхронные компоненты — можно загружать компоненты "лениво", по мере необходимости, что повышает производительность больших приложений. Динамические компоненты — можно переключаться между разными компонентами с помощью `< component :is="currentComponent">`. Функциональные компоненты (в Options API) — компоненты без состояния, работающие как чистые функции от своих props. [Сравнение с другими фреймворками]
[Преимущества подхода Vue к компонентам] 1. Ясность и структурированность — код организован логично, его легко читать и поддерживать. 2. Лёгкость переиспользования — компоненты становятся строительными блоками для всей кодовой базы. 3. Эффективность командной работы — разные разработчики могут работать над разными компонентами, не мешая друг другу. 4. Тестируемость — изолированный компонент проще покрыть модульными тестами. Концепция компонентов — это сердце Vue. Она превращает разработку интерфейса из написания скриптов в проектирование системы взаимодействующих, переиспользуемых блоков. Именно эта концепция позволяет Vue-приложениям оставаться поддерживаемыми и масштабируемыми. [Создание компонентов приложения Vue] Общепринятая концепция - держать помещать каждый компонент в отдельную директорию components, которая находится в папке src. Далее показан пошаговый пример создания компонента для кнопки. 1. Создайте в папке src папку components. 2. В папке components создайте файл Button.vue. В этом файле создайте 3 пустые секции: < script setup>< /script> Такой единый шаблон применяется для всех компонентов, в том числе и для компонента самого приложения App. Однако файл приложения компонента App.vue находится в непосредственно в папке src. 3. В теге template создайте вложенный тег button: < template> < button>Кнопка< /button> < /template> Это необходимый минимум, который нужен для создания компонента. Чтобы использовать этот компонент в другом (родительском) компоненте, его необходимо импортировать. 4. Откройте файл App.vue, и добавьте в его тег < script setup> директиву импорта: import MyButton from "./components/Button.vue" Здесь вместо MyButton может быть любое осмысленное имя. 5. Добавьте MyButton в шаблон App.vue: < template> < MyButton /> < /template> [Добавление стиля к компоненту] 1. Добавьте в файле компонента Button.vue, в теге button атрибут class: < template> < button class="button">Кнопка< /button> < /template> Здесь class="button" создает ссылку на класс CSS. Имя класса может быть произвольным, главное чтобы оно совпадало с именем в описании стиля. 2. В файле компонента Button.vue, в теге style scoped создайте описание стиля класса button: < style scoped> .button { scoped в теге < style scoped> компонентов Vue используется для ограничения области действия CSS-стилей только текущим компонентом. Если убрать scoped, то стиль для компонента станет глобальным, и применится ко всему проекту. Как это работает: 1. Изоляция стилей - с использованием scoped стили применяются только к элементам текущего компонента. 2. Автоматическая генерация атрибутов - Vue добавляет уникальные data-атрибуты к элементам компонента. 3. Преобразование CSS-селекторов - селекторы модифицируются так, чтобы соответствовать только элементам с этими атрибутами. Пример преобразования: < template> < div class="button">Нажми меня< /div> < /template> Vue преобразует это в примерно такой HTML-код: < div data-v-f3f3eg9 class="button">Нажми меня< /div> Преимущества: - Предотвращение конфликтов стилей между компонентами Ограничения: Глубокие селекторы - для стилизации дочерних компонентов нужно использовать `::v-deep`, `:deep()` или `>>>`: :deep(.child-class) { Глобальные стили - если нужны глобальные стили, используйте < style> без scoped, или отдельный CSS-файл. Когда использовать scoped: - Компоненты многократного использования - для предотвращения стилевых конфликтов. Когда не использовать: - Глобальные стили (reset.css, переменные CSS). scoped - один из ключевых инструментов Vue для создания поддерживаемых и изолированных компонентов. 3. В определении стиля можно использовать переменные из корневого файла style.css с помощью var. Например, в файле style.css есть определение градиента --gradient: :root { Тогда это определение можно применить к компоненту следующим образом. В файле Button.vue добавьте background: var(--gradient);: < style scoped> .button { Пример полного настроенного стиля для кнопки: < style scoped> .button { Этот scoped стиль определяет стилизацию для кнопки в Vue-компоненте с изоляцией. Давайте расшифруем каждое свойство: border: none; - убирает стандартную рамку кнопки border-radius: 10px; - скругляет углы на 10 пикселей background: var(--gradient); - задаёт градиентный фон через CSS-переменную --gradient padding: 14px; - внутренние отступы со всех сторон font-family: var(--font); - шрифт через CSS-переменную --font font-size: 20px; - размер шрифта 20px (крупный, для акцента) font-weight: 600; - полужирное начертание (полужирное) color: var(--primary); - цвет текста через CSS-переменную --primary cursor: pointer; - меняет курсор на "руку" при наведении Состояние при наведении .button:hover: background: var(--gradient-inverted); - меняет градиент при наведении (инвертированный вариант, описанный в CSS-переменной --gradient-inverted) Ключевые особенности: Использование CSS-переменных (Custom Properties): --gradient, --gradient-inverted - градиенты для разных состояний Это говорит о том, что тема компонента настраивается через глобальные CSS-переменные, что позволяет: - Легко менять тему всего приложения Визуальные характеристики кнопки: Интерактивная - меняет фон при наведении Что означает scoped в этом контексте: стили применятся ТОЛЬКО к элементам с классом .button внутри этого конкретного компонента, даже если в других компонентах есть элементы с таким же классом. Как будет выглядеть кнопка в разметке: < button class="button" data-v-abc123>Текст кнопки< /button> [Тег slot] С помощью тега < slot>< /slot> в компоненте можно передавать содержимое из компонента-родителя (обычно это компонент приложения App.vue) в дочерний компонент. Пример для дочернего компонента нашей кнопки показан ниже. Файл App.vue: < script setup> В файле Button.vue: < template> < button class="button"> < slot>< /slot> < /button> < /template> Тег < slot> в этом примере выполняет критически важную функцию — это система content projection Vue, аналогичная `children` в React или transclusion в Angular. В результате содержимое из < MyButton>< /MyButton> будет перенесено на компонент кнопки (в этом примере текст "Сохранить"). Замечание: тег < slot>< /slot> можно заменить на самозакрывающийся < slot />, что дает то же самое. В компоненте Button.vue тег slot определит место для вставки контента из родителя: < button class="button"> < slot>< /slot> < /button> < slot> — это placeholder, который говорит: "Здесь будет отображаться контент, который передаст родительский компонент". В компоненте App.vue: < MyButton>Сохранить< /MyButton> Текст "Сохранить" попадает внутрь < slot>< /slot> компонента Button.vue. < !-- Результат рендеринга в HTML --> < button class="button" data-v-код-компонента> Сохранить < /button> Аналогия из реальной жизни: представьте шаблон кнопки как рамку для фото: - Button.vue — сама рамка с определённым дизайном Преимущества использования слотов: 1. Переиспользуемость компонентов. Один компонент кнопки можно использовать с разным текстом: < MyButton>Отправить< /MyButton> < MyButton>Удалить< /MyButton> < MyButton>Отмена< /MyButton> 2. Гибкость содержимого. В слот можно передавать не только текст, но и другие элементы. Например в App.vue: < template> < MyButton>< span class="icon">+< /span> Сохранить< /MyButton> < MyButton>< strong>ВАЖНО:< /strong>Подтвердить< /MyButton> < /template> 3. Разделение ответственности: - Button.vue отвечает за внешний вид, поведение, доступность 4. Fallback контент. Можно задать контент по умолчанию: < !-- Button.vue --> < button class="button"> < slot>Кнопка< /slot> < !-- "Кнопка" отобразится если слот пустой --> < /button> Без слота пришлось бы: Вариант 1. Использовать props, что хуже, потому что нельзя передавать HTML/компоненты. < !-- Button.vue --> < button class="button">{{ text }}< /button> Вариант 2. Дублировать код, что намного хуже - нарушение принципа DRY (Don't Repeat Yourself), сложное обновление. < !-- App.vue --> < button class="button">Сохранить< /button> < button class="button">Отправить< /button> < button class="button">Удалить< /button> [Несколько slot] В компоненте можно применить несколько slot, используемых для разных целей. Для этого в slot вставляется атрибут name. Например для компонента Button.vue: < template> < button class="button"> < slot name="icon" /> < slot /> < /button> < /template> В этом примере определено 2 слота (оба самозакрывающиеся), один с именем icon, а второй безымянный. Можно определить произвольное количество именованных слотов с уникальными именами. Имя слота (в данном примере "icon") произвольное, и оно используется для ссылки на этот слот в родительском компоненте. Слот, который не имеет имени называется слотом по умолчанию, все что будет положено в дочерний компонент, попадет в этот слот. В родительском компоненте несколько слотов используются с помощью вложенных тегов template и директивы v-slot, например: < template> < MyButton> < template v-slot:icon>+< /template> Сохранить < /MyButton> < /template> Также существует сокращенный вариант директивы v-slot, '#': < template> < MyButton> < template #icon>+< /template> Сохранить < /MyButton> < /template> [Динамический слот] < template> < MyButton> < template #[имя_переменной]>+< /template> Сохранить < /MyButton> < /template> Здесь имя_переменной это имя JavaScript переменной или константы. Подразумевается, что у нас определен слот, имя которого совпадает с имя_переменной. [Ссылки] |