Vue. Пути улучшения возможностей повторного использования ваших компонентов

В этой статье хотелось бы начать освещать вопрос лучшего опыта повторного использования Vue компонентов. Разработчики часто ведут поиск информации на тему наследования компонентов во Vue. Вероятно, что это связано с тем, что наследование – самый очевидный и частый способ, которым обычно программисты добиваются повторного использования кода, если требуется обеспечить похожее (но с некоторыми отличиями) с имеющимся поведение.

Но повторное использование можно (и даже оптимальнее) осуществлять за счет других подходов. Набор приемов куда как более велик. Предлагаю вам поразмышлять на эту тему вместе с автором в нескольких статьях.

Содержание

Использование свойств. Функции свойств

Рассмотрим к каким же способам разработчики прибегают для расширения сферы использования имеющихся компонентов. Часть из этих подходов используется широко и явно, другая часть менее известна и очевидна.

Рассмотрим такой инструмент, как свойства компонента. Не смотря на кажущуюся свою простоту, свойства могут выступать в различных ролях. Можно выделить следующие из них:

  • Передача данных в компонент для работы с ними. Например, для отображения или редактирования.
  • Управление поведением компонента. То есть свойства-настройки для конфигурации компонента. Выбор режим работы компонента из имеющихся.
  • Шаблонные свойства. Для отображения кусочка разметки внутри компонента.

Взглянем чуть более подробно на каждый вариант использования свойств.

Передача данных через свойства

Первый способ – самый простой и частоупотребимый. У нас есть, скажем, выпадающий список (dropdown или select), которому мы можем передать массив возможных вариантов и текущее значение. Шаблон компонента ведет себя одинаково каждый раз.

Внешний вид компонента dropdown
Внешний вид компонента dropdown из Vuetify

Список при использовании его в разных местах приложения будет отличаться лишь набором доступных значений. Например, с помощью одного такого компонента мы могли бы выбирать валюту ценников в магазине, а с помощью другого – порядок сортировки товаров.

Свойства для настройки компонента

Второй способ использования свойств – конфигурация компонента. Например, мы могли бы добавить свойство multiple нашему списку, чтобы дать возможность выбирать несколько значений. Список будет часть своей функциональности сохранять, но появится и новая. По-прежнему будут отображаться варианты (опции) один под другим, однако придется для каждого из них отобразить галочку (checkbox).

Компонент dropdown из Vuetify с множественным выбором

Шаблонные свойства

Третий способ использования свойств – шаблонные свойства. Простой пример – надпись на кнопке. Мы могли бы передать свойство 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 места для вставки иконок или кнопок. Два места – впереди и два места – в конце. Два внутренних и два внешних.

Части компонента текстового поля из документации Vuetufy
Части компонента текстового поля из документации Vuetufy

В нашем компоненте мы, конечно, могли бы использовать для этого свойства-настройки, но это ограничило бы нас в применении иконок узким их кругом (только поддерживаемые в коде компонента). А если мы захотим вставить не иконку, а кнопку? У кнопки должна быть возможность обработки события нажатия. А если нужно будет вставить еще более сложный 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>
Результат использование компонента со слотами

Код примера доступен в репозитории.

Полученный код может использоваться как по месту, так и для создания нового компонента со слегка отличающимся поведением. Это в целом было бы очень похоже на наследование.

Более явно все выгоды будут видны, если подумать о том, что исходный компонент может быть не просто текстовым полем, а иметь более широкую функциональность. Например, под полем могла бы выводиться ошибка красным текстом. Для поля могла бы задаваться маска (ограничения формата ввода). В этих случаях нам выгоднее иметь в исходном компоненте возможность расширяемости, чем создавать компонент с нуля.

При этом разработчик, который создает исходный компонент просто закладывает точки, в которые можно будет помещать дополнительные части шаблона. Ему не надо продумывать как именно это будет использоваться. Решать то, каким будет производный компонент будет в большей степени тот, кто будет создавать производный. В то время как со свойствами жестко очертить круг возможностей должен разработчик исходного компонента.

Чем более велики прямые возможности компонента, тем труднее создавать такой же с немного отличающимся поведением. Например, таблица, которая отображает данные и имеет сортировки и разбивку на страницы, не такой простой компонент. Если нам надо будет переопределить нижнюю часть с кнопками навигации по страницам, то было бы хорошо иметь возможность сделать это без дублирования кода для работы основной части.

Расширенные возможности слотов

Однако, наиболее интересные варианты использования слотов связаны с передачей данных от внутреннего компонента к внешнему. В каких случаях это может понадобиться, как работает и какие выгоды дает будет рассказано в следующей статье.

Также в последующих статьях будет описано как унаследовать функциональность компонента не потеряв работоспособность имеющихся в шаблоне слотов.

Чтобы не пропустить очередную статью, подпишитесь на обновления.

Оставить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *