Статьи серии
- Vue. Пути улучшения возможностей повторного использования ваших компонентов
- Таблица на Vue 3. Создание компонента
- Таблица на Vue 3. Добавляем Scoped Slots
- Vue 3 expose
- Vue. Продвинутая работа со слотами
- Есть ли наследование во Vue.js
- Передача данных на много уровней Vue компонентов
В этой статье хотелось бы начать освещать вопрос лучшего опыта повторного использования Vue компонентов. Разработчики часто ведут поиск информации на тему наследования компонентов во Vue. Вероятно, что это связано с тем, что наследование – самый очевидный и частый способ, которым обычно программисты добиваются повторного использования кода, если требуется обеспечить похожее (но с некоторыми отличиями) с имеющимся поведение.
Но повторное использование можно (и даже оптимальнее) осуществлять за счет других подходов. Набор приемов куда как более велик. Предлагаю вам поразмышлять на эту тему вместе с автором в нескольких статьях.
Содержание
Использование свойств. Функции свойств
Рассмотрим к каким же способам разработчики прибегают для расширения сферы использования имеющихся компонентов. Часть из этих подходов используется широко и явно, другая часть менее известна и очевидна.
Рассмотрим такой инструмент, как свойства компонента. Не смотря на кажущуюся свою простоту, свойства могут выступать в различных ролях. Можно выделить следующие из них:
- Передача данных в компонент для работы с ними. Например, для отображения или редактирования.
- Управление поведением компонента. То есть свойства-настройки для конфигурации компонента. Выбор режим работы компонента из имеющихся.
- Шаблонные свойства. Для отображения кусочка разметки внутри компонента.
Взглянем чуть более подробно на каждый вариант использования свойств.
Передача данных через свойства
Первый способ – самый простой и частоупотребимый. У нас есть, скажем, выпадающий список (dropdown или select), которому мы можем передать массив возможных вариантов и текущее значение. Шаблон компонента ведет себя одинаково каждый раз.
Список при использовании его в разных местах приложения будет отличаться лишь набором доступных значений. Например, с помощью одного такого компонента мы могли бы выбирать валюту ценников в магазине, а с помощью другого – порядок сортировки товаров.
Свойства для настройки компонента
Второй способ использования свойств – конфигурация компонента. Например, мы могли бы добавить свойство multiple
нашему списку, чтобы дать возможность выбирать несколько значений. Список будет часть своей функциональности сохранять, но появится и новая. По-прежнему будут отображаться варианты (опции) один под другим, однако придется для каждого из них отобразить галочку (checkbox).
Шаблонные свойства
Третий способ использования свойств – шаблонные свойства. Простой пример – надпись на кнопке. Мы могли бы передать свойство caption
с названием. В простейшем случае это будет просто текст, интерполированный в шаблоне. В более сложном такое свойство может содержать разметку.
<script setup>
const props = defineProps({
caption: String
})
</script>
<template>
<button>{{ props.caption }}</button>
</template>
<AppButton caption="Submit"></AppButton>
Свойства передающие функции
Есть еще один вариант использования свойств. Это передача через свойство некоторой функции, что будет определять поведение компонента. Для реализации, например, паттерна Стратегия. Компонент в таком случае умеет работать с алгоритмами, которые передаются ему извне.
Однако здесь надо учитывать семантику. Если передаваемая функция будет использована как callback, то более верным подходом здесь было бы использовать не свойство с передачей функции, а событие компонента и обработчик.
Ограниченность свойств
Однако применение свойств имеет свои пределы и недостатки.
Недостатки свойств-настроек
Свойства-настройки можно применять для расширения функциональности. Но в определенный момент мы придем к тому, что с каждым новым свойством работать с компонентом будет сложнее. Действительно, когда появляются десятки свойств, то найти нужное сложнее. И сам компонент становится невероятно сложным, когда ему необходимо поддерживать множество вариантов поведения.
Если вы пришли к такой ситуации в своем компоненте, то ее можно пытаться решить различными способами. Например, создать базовый компонент (с основной функциональностью) и унаследовать от него узкоспециализированные. Или же дробить компонент на более мелкие для решения частей текущей задачи компонента.
Ограничения шаблонных свойств
Шаблонные свойства хороши, когда в них передается просто текст. Однако, когда там появляется разметка и она становится все больше, то пользоваться этим становится все сложнее. И наконец, когда разметка станет содержать реактивные данные, мы окончательно ощутим неэргономичность такого подхода.
Конечно, это не значит, что свойства в этой роли не стоит использовать. Просто их использование имеет свои пределы. После достижения некоторой точки нам придется прибегнуть к другим подходам.
Слоты
Поиск иных пулей приведет нас к слотам. Слоты – это очень мощный инструмент, который может сделать ваши компоненты расширяемыми. Его нельзя недооценивать. Есть несколько неочевидных способов применения слотов. Можно даже сказать трюков.
Замена шаблонного свойства слотом
Самый простой и понятный вариант использования слотов – замена шаблонного свойства. Когда вставляемая в компонент разметка становится сложной и имеет реактивные данные, то вместо свойства сильно удобнее использовать слот.
<template>
<button><slot></slot></button>
</template>
<AppButton>Send <span class="strong-text">message</span> to author</AppButton>
Наличие нескольких слотов в разных частях компонента
Особенно удобно, что слотов может быть множество. Мы можем менять части нашего компонента. То есть повторно использовать его код в большем числе случаев.
Рассмотрим компонент, который представляет собой текстовое поле с иконкой и кнопкой.
<script setup lang="ts">
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
</script>
<template>
<div class="text-input-container">
<FontAwesomeIcon :icon="faSearch" class="icon" />
<input type="search" name="search" class="field" placeholder="Search..." />
<input type="submit" value="Search" class="button" />
</div>
</template>
<TextInput></TextInput>
Было бы удобно дать возможность размещать различные иконки. Библиотека UI компонентов Vuetify, к примеру, содержит поле ввода, в котором есть 4 места для вставки иконок или кнопок. Два места – впереди и два места – в конце. Два внутренних и два внешних.
В нашем компоненте мы, конечно, могли бы использовать для этого свойства-настройки, но это ограничило бы нас в применении иконок узким их кругом (только поддерживаемые в коде компонента). А если мы захотим вставить не иконку, а кнопку? У кнопки должна быть возможность обработки события нажатия. А если нужно будет вставить еще более сложный html-код? При использовании свойств мы столкнемся с ограничениями описанными выше.
Но мы бы могли использовать слоты. Тогда в определенных местах компонента объявим их:
<template>
<div class="text-input-container">
<slot name="prepend"></slot>
<input type="text" name="search" class="field"/>
<slot name="append"></slot>
</div>
</template>
И при использовании компонента передадим нужные кусочки шаблонов.
<div class="row">
<TextInput></TextInput>
</div>
<div class="row">
<TextInput>
<template #prepend>
<FontAwesomeIcon :icon="faSearch" class="icon" />
</template>
<template #append>
<input type="submit" value="Search" class="button" />
</template>
</TextInput>
</div>
<div class="row">
<TextInput>
<template #append>
<FontAwesomeIcon :icon="faEnvelope" class="icon" />
</template>
</TextInput>
</div>
Код примера доступен в репозитории.
Полученный код может использоваться как по месту, так и для создания нового компонента со слегка отличающимся поведением. Это в целом было бы очень похоже на наследование.
Более явно все выгоды будут видны, если подумать о том, что исходный компонент может быть не просто текстовым полем, а иметь более широкую функциональность. Например, под полем могла бы выводиться ошибка красным текстом. Для поля могла бы задаваться маска (ограничения формата ввода). В этих случаях нам выгоднее иметь в исходном компоненте возможность расширяемости, чем создавать компонент с нуля.
При этом разработчик, который создает исходный компонент просто закладывает точки, в которые можно будет помещать дополнительные части шаблона. Ему не надо продумывать как именно это будет использоваться. Решать то, каким будет производный компонент будет в большей степени тот, кто будет создавать производный. В то время как со свойствами жестко очертить круг возможностей должен разработчик исходного компонента.
Чем более велики прямые возможности компонента, тем труднее создавать такой же с немного отличающимся поведением. Например, таблица, которая отображает данные и имеет сортировки и разбивку на страницы, не такой простой компонент. Если нам надо будет переопределить нижнюю часть с кнопками навигации по страницам, то было бы хорошо иметь возможность сделать это без дублирования кода для работы основной части.
Расширенные возможности слотов
Однако, наиболее интересные варианты использования слотов связаны с передачей данных от внутреннего компонента к внешнему. В каких случаях это может понадобиться, как работает и какие выгоды дает будет рассказано в следующей статье.
Также в последующих статьях будет описано как унаследовать функциональность компонента не потеряв работоспособность имеющихся в шаблоне слотов.
Чтобы не пропустить очередную статью, подпишитесь на обновления.