Wrapper компоненты

Wrapper-компоненты — это абстракции, позволяющие инкапсулировать работу с библиотекой Driver.js внутри переиспользуемых модулей. Их основная задача — скрыть низкоуровневые детали настройки и управления турами, предоставляя удобный интерфейс для интеграции в приложение.

Такие компоненты особенно актуальны при использовании современных фреймворков (React, Vue, Angular), где важна декларативность и повторное использование логики.


Назначение wrapper-компонентов

Основные функции wrapper-слоя:

  • централизованное управление конфигурацией туров
  • упрощение запуска и остановки сценариев
  • интеграция с состоянием приложения
  • минимизация дублирования кода
  • обеспечение совместимости с архитектурой фреймворка

Базовая структура wrapper-компонента

Wrapper-компонент строится вокруг экземпляра Driver:

import Driver from "driver.js";
import "driver.js/dist/driver.css";

class DriverWrapper {
  constructor(options = {}) {
    this.driver = new Driver({
      animate: true,
      opacity: 0.75,
      ...options
    });
  }

  defineSteps(steps) {
    this.driver.defineSteps(steps);
  }

  start() {
    this.driver.start();
  }

  reset() {
    this.driver.reset();
  }
}

export default DriverWrapper;

Ключевые элементы:

  • хранение экземпляра Driver
  • методы управления (start, reset)
  • возможность переопределения настроек

Инкапсуляция конфигурации

Wrapper позволяет вынести конфигурацию в единое место:

const defaultSteps = [
  {
    element: "#header",
    popover: {
      title: "Заголовок",
      description: "Описание заголовка"
    }
  },
  {
    element: "#button",
    popover: {
      title: "Кнопка",
      description: "Нажмите сюда"
    }
  }
];

Использование:

const tour = new DriverWrapper();
tour.defineSteps(defaultSteps);
tour.start();

Wrapper как сервис

В более сложных приложениях wrapper реализуется как сервис:

class TourService {
  constructor() {
    this.driver = null;
  }

  init(options) {
    this.driver = new Driver(options);
  }

  start(steps) {
    if (!this.driver) return;
    this.driver.defineSteps(steps);
    this.driver.start();
  }

  stop() {
    this.driver?.reset();
  }
}

export const tourService = new TourService();

Преимущества:

  • единая точка управления
  • возможность глобального доступа
  • упрощение интеграции в разные модули

Wrapper в React

В React wrapper обычно реализуется как компонент или hook.

Вариант с hook

import { useRef } from "react";
import Driver from "driver.js";

export function useDriver(options) {
  const driverRef = useRef(null);

  const init = () => {
    driverRef.current = new Driver(options);
  };

  const start = (steps) => {
    if (!driverRef.current) init();
    driverRef.current.defineSteps(steps);
    driverRef.current.start();
  };

  const reset = () => {
    driverRef.current?.reset();
  };

  return { start, reset };
}

Использование:

const { start } = useDriver();

start(steps);

Вариант с компонентом

import { useEffect } from "react";
import Driver from "driver.js";

const DriverComponent = ({ steps, run }) => {
  useEffect(() => {
    if (!run) return;

    const driver = new Driver();
    driver.defineSteps(steps);
    driver.start();

    return () => driver.reset();
  }, [run, steps]);

  return null;
};

export default DriverComponent;

Wrapper в Vue

В Vue wrapper реализуется через composable или компонент.

Composable

import Driver from "driver.js";

export function useDriver(options = {}) {
  const driver = new Driver(options);

  const start = (steps) => {
    driver.defineSteps(steps);
    driver.start();
  };

  const reset = () => driver.reset();

  return { start, reset };
}

Wrapper в Angular

В Angular используется сервис:

import { Injectable } from "@angular/core";
import Driver from "driver.js";

@Injectable({
  providedIn: "root"
})
export class DriverService {
  private driver: any;

  init(options: any) {
    this.driver = new Driver(options);
  }

  start(steps: any[]) {
    this.driver.defineSteps(steps);
    this.driver.start();
  }

  reset() {
    this.driver.reset();
  }
}

Управление состоянием через wrapper

Wrapper-компонент может быть интегрирован с состоянием приложения:

class DriverWithState {
  constructor(store) {
    this.store = store;
    this.driver = new Driver();
  }

  start(steps) {
    this.store.setTourActive(true);
    this.driver.defineSteps(steps);
    this.driver.start();
  }

  stop() {
    this.store.setTourActive(false);
    this.driver.reset();
  }
}

Динамическое создание шагов

Wrapper может генерировать шаги на основе состояния:

generateSteps(userRole) {
  if (userRole === "admin") {
    return [
      { element: "#adminPanel", popover: { title: "Админка" } }
    ];
  }

  return [
    { element: "#dashboard", popover: { title: "Главная" } }
  ];
}

Расширение wrapper-компонента

Добавление дополнительных возможностей:

Логирование

start(steps) {
  console.log("Tour started");
  this.driver.defineSteps(steps);
  this.driver.start();
}

Обработчики событий

this.driver = new Driver({
  onReset: () => console.log("Tour ended"),
  onNext: () => console.log("Next step")
});

Lazy инициализация

Оптимизация через отложенное создание:

getDriver() {
  if (!this.driver) {
    this.driver = new Driver();
  }
  return this.driver;
}

Управление несколькими турами

Wrapper может поддерживать несколько сценариев:

class MultiTour {
  constructor() {
    this.tours = {};
  }

  create(name, steps) {
    const driver = new Driver();
    driver.defineSteps(steps);
    this.tours[name] = driver;
  }

  start(name) {
    this.tours[name]?.start();
  }
}

Паттерны проектирования

Wrapper-компоненты часто реализуют:

  • Facade — упрощение интерфейса Driver.js
  • Singleton — единый экземпляр тура
  • Factory — создание разных конфигураций
  • Strategy — выбор сценария тура

Ошибки при реализации wrapper

1. Повторная инициализация Driver

start() {
  const driver = new Driver(); // ошибка
}

Решение — хранить экземпляр.


2. Отсутствие очистки

componentWillUnmount() {
  // driver.reset() не вызван
}

3. Жёсткая привязка к DOM

Wrapper должен учитывать, что элементы могут появляться асинхронно.


Практическая архитектура

Оптимальная структура:

/tour
  ├── driverWrapper.js
  ├── steps.js
  ├── tourService.js
  └── hooks/

Расширяемость и масштабирование

Wrapper-компонент должен поддерживать:

  • добавление новых туров без изменения ядра
  • конфигурацию через внешние файлы
  • интеграцию с аналитикой
  • условный запуск (например, только для новых пользователей)

Интеграция с аналитикой

start(steps) {
  analytics.track("tour_started");
  this.driver.defineSteps(steps);
  this.driver.start();
}

Асинхронные шаги

Wrapper может ожидать загрузку данных:

async startAsync(getSteps) {
  const steps = await getSteps();
  this.driver.defineSteps(steps);
  this.driver.start();
}

Управление доступом

startIfAllowed(user) {
  if (!user.hasSeenTour) {
    this.start(this.getSteps());
  }
}

Кастомизация поведения

Wrapper может централизованно задавать стили:

this.driver = new Driver({
  className: "custom-theme",
  nextBtnText: "Далее",
  prevBtnText: "Назад"
});

Связь с UI-компонентами

Wrapper может взаимодействовать с интерфейсом:

onStepChange(callback) {
  this.driver = new Driver({
    onHighlighted: callback
  });
}

Повторное использование

Главная ценность wrapper-компонентов:

  • единый API
  • независимость от конкретного фреймворка
  • лёгкость тестирования
  • возможность замены библиотеки без переписывания логики приложения