Определение шаблона адаптера

Шаблон адаптера — это структурный шаблон проектирования, который позволяет несовместимым объектам взаимодействовать.

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

Почему таким образом?

Я перечислю несколько причин, и вы сможете узнать свою

  • ответственность — Если вы посмотрите на код ниже, вы заметите, что каждый класс за что-то отвечает. В отличие от того, когда мы набираем код «спагетти»
  • Разделение ответственности — как я уже упоминал, каждый класс за что-то отвечает и делится на слои.
    — modules — папка, содержащая логику создания экземпляра класса (позже объясню, почему это важно)
    — файлы констант — необходимые строки, заполнители которых должны быть неизменяемыми в коде.
    — …
  • сотрудничество
    — от BE к FE — например, что, если настройка должна быть выполнена на стороне BE редактором контента? Что он выбирает, как будет выглядеть карусель
    — команда — если выбор будет внутри более широкой команды людей. В проекте несколько команд, обслуживающих одного клиента с несколькими проектами. Например, карусель Bootstrap используется на всех сайтах, но все используют ее по-разному.
  • логика — а что, если помимо видимой части карусель должна выполнять дополнительную логику?
    — например, при воспроизведении видео на одном слайде оно останавливается, переключаясь на другой. Будем ли мы распространять логику и где она будет определена? Должен ли класс карусели напрямую заботиться о других компонентах?
    — несколько каруселей с разными конфигурациями на одной странице.
  • читабельность — мне доводилось натыкаться на код, класс которого имеет почти такое же количество строк, как и сам плагин.

Примеры

Пример Swiper: https://codesandbox.io/s/carousel-swiper-x7owvm

Пример крошечного слайдера: https://codesandbox.io/s/carousel-tiny-slider-1gou1p

Примечание. Если некоторые примеры не запускаются, нажмите кнопку обновления на вкладке браузера или откройте в новом окне.

Руководство по внедрению

import carouselModule from "./modules/carousel-module";

carouselModule();

Мы вызываем модуль, отвечающий за логику инстанцирования нашего класса. файл: carousel-module.ts

import Carousel from "../carousel/carousel";
import "../styles.css";

export default () => {
  const carouseles = document.querySelectorAll(".js-carousel");
  if (carouseles) {
    carouseles.forEach((carousel) => new Carousel(carousel as HTMLElement));
  }
};
  • Наш класс карусели не должен интересоваться, готова ли DOM, есть ли js-карусель на странице или нет.
  • Наш класс не должен использовать манипуляции с DOM и операторы if. Модули — это те, которые должны заботиться о том, как создается экземпляр класса.
  • Это также место, где вы, например, будете выполнять динамический импорт в сборке веб-пакета. Когда вы хотите сделать кусочки

Например вот так

export default () => {
  const carouselElements = document.querySelectorAll('.js-carousel');
  if (carouselElements.length) {
    carouselElements.forEach(async (element) => {
      const { default: Carousel } = await import(/* webpackChunkName: "carousel" */ '../components/carousel/carousel')

      new Carousel(element as HTMLElement);
    });
  }
  return null;
};

Карусель класс

Свайпер

constructor(element: HTMLElement) {
    this.carouselElement = element;
    this.desktopDevice = window.matchMedia("(min-width: 1024px)");
    // Class for Plugin API
    this.carouselEvents = new CarouselEvents();
    Swiper.use([Navigation, Pagination, Grid, Lazy, Autoplay]);

    this.init();

    this.desktopDevice.addListener(this.breakPointHandler.bind(this));
}

Крошечный слайдер

constructor(element: HTMLElement) {
    this.carouselElement = element;
    this.desktopDevice = window.matchMedia("(min-width: 1024px)");
    // Class for Plugin API
    this.carouselEvents = new CarouselEvents();

    this.init();

    this.desktopDevice.addListener(this.breakPointHandler.bind(this));
}
  • конструктор устанавливает элемент, связанный с каруселью.
  • прослушиватель настроен для отслеживания изменений для определенного медиа-запроса. (дополнительная логика для отслеживания медиа-запроса, если вы хотите показать или создать карусель только на рабочем столе или только на мобильном телефоне)
  • адаптер карусели и сама карусель создаются с помощью метода init.

На первый взгляд разницы никакой, кроме строчки кода, куда включены карусельные модули Swiper.

Swiper.use([Navigation, Pagination, Grid, Lazy, Autoplay]);

В следующем разделе файла оба класса имеют одинаковую логику

init = () => this.createCarousel();

функция инициализации создать карусель

карусель

createCarousel = (carousel: HTMLElement) => {
    ...
    this.carousel = new Swiper(this.carouselElement, {
      ...defaultOptions,
      ...optionsFromElement
    });

  this.carouselEvents.registerEvents(this.carousel, this.carouselElement);
};

крошечный слайдер

createCarousel = (carousel: HTMLElement) => {
    ...
    this.carousel = tns({
      container: this.carouselElement,
      ...defaultOptions,
      ...optionsFromElement
    });
    this.carouselEvents.registerEvents(this.carousel, this.carouselElement);
};

Единственное отличие заключается в объекте конфигурации. Для swiper элемент карусели является отдельным параметром, а для Tiny Slider — частью объекта конфигурации.

Наконец, мы подошли к классу CarouselEvents.

carousel.events.ts swiper

export default class CarouselEvents {
  private carousel: Swiper;
  private element: HTMLElement;

  registerEvents(carousel: Swiper, element: HTMLElement) {
    this.carousel = carousel;
    this.element = element;
    this.carousel.on(CAROUSEL_CHANGE, () => this.stopVideos());
  }
}

carousel.events.ts крошечный слайдер

export default class CarouselEvents {
  private carousel: TinySliderInstance;
  private element: HTMLElement;

  registerEvents(carousel: TinySliderInstance, element: HTMLElement) {
    this.carousel = carousel;
    this.element = element;
    this.carousel.events.on(CAROUSEL_CHANGE, () => this.stopVideos());
  }
}

Разница? Ну, только интерфейс карусели и то, как регистрируются события.

константы свайпера

export const CAROUSEL_CHANGE = "slideChange";

крошечные константы ползунка

export const CAROUSEL_CHANGE = "indexChanged";

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

Swiper регистрирует события с помощью

this.carousel.on(CAROUSEL_CHANGE, callback);

Крошечный ползунок регистрирует события с помощью

this.carousel.events.on(CAROUSEL_CHANGE, callback);

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

Если мы изменим карусель на третий тип, например. карусель Bootstrap, код будет следующим

из документации

$('#myCarousel').on('slide.bs.carousel', function () {// do something…})

константы.ts

export const CAROUSEL_CHANGE = "slide.bs.carousel";

карусель.events.ts

this.element.on(CAROUSEL_CHANGE, callback)

Заключение

  • Мы создали карусель, которая не зависит от типа используемого плагина.
  • Мы можем создать много экземпляров с разными конфигурациями, не создавая разные типы карусели или экземпляры.
  • Минимальное изменение разметки кода. Просто чтобы следовать структуре плагина. Конфигурация соответствует плагину.
  • Логика реализации
  • создание карусели только на мобильном устройстве (логика может быть расширена)
  • мониторинг событий и реагирование на события (логика может быть расширена)