Типичные антипаттерны

Одним из наиболее распространённых антипаттернов при работе со Slim Select становится многократное создание экземпляра на одном и том же <select> без корректного уничтожения предыдущего.

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

Проблемный сценарий:

new SlimSelect({
  select: '#country'
});

new SlimSelect({
  select: '#country'
});

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

Корректный подход требует явного управления жизненным циклом:

const instance = new SlimSelect({
  select: '#country'
});

instance.destroy();

const nextInstance = new SlimSelect({
  select: '#country'
});

Игнорирование метода destroy и утечки памяти

Slim Select создаёт собственные DOM-структуры, подписки на события и внутренние состояния. Пренебрежение вызовом destroy() при удалении компонентов приводит к накоплению неиспользуемых узлов и обработчиков.

Типичная ошибка возникает в SPA-приложениях при смене маршрутов:

// компонент уничтожается, но SlimSelect остаётся активным
mounted() {
  this.select = new SlimSelect({ select: '#city' });
}

unmounted() {
  // отсутствует destroy
}

Правильное управление жизненным циклом:

mounted() {
  this.select = new SlimSelect({ select: '#city' });
}

unmounted() {
  this.select?.destroy();
}

Прямое манипулирование DOM вместо API библиотеки

Slim Select поддерживает синхронизацию состояния через API, однако попытки вручную изменять <option> элементы приводят к рассинхронизации UI и внутреннего состояния.

Антипаттерн:

document.querySelector('#country').innerHTML = `
  <option value="kz">Kazakhstan</option>
  <option value="ru">Russia</option>
`;

После такого изменения Slim Select не обновляет внутренний список автоматически.

Правильный подход — использовать методы обновления:

select.setData([
  { text: 'Kazakhstan', value: 'kz' },
  { text: 'Russia', value: 'ru' }
]);

Частое злоупотребление setData при небольших изменениях

Полная перезагрузка данных через setData при каждом изменении одного элемента приводит к избыточной переработке DOM и потере состояния выбора.

Антипаттерн:

data.push({ text: 'New item', value: 'new' });

select.setData(data);

При частых вызовах это создаёт лишние пересоздания списка.

Более устойчивый подход — минимизация изменений:

const current = select.getData();
select.setData([...current, { text: 'New item', value: 'new' }]);

Отсутствие контроля асинхронных обновлений

При загрузке данных с сервера часто возникает гонка запросов, из-за которой более старый ответ перезаписывает актуальные данные.

Проблемный сценарий:

fetch('/api/countries')
  .then(r => r.json())
  .then(data => {
    select.setData(data);
  });

Если запрос выполняется несколько раз подряд, порядок ответов становится неконтролируемым.

Устойчивый вариант — введение версии запроса:

let requestId = 0;

function loadCountries() {
  const id = ++requestId;

  fetch('/api/countries')
    .then(r => r.json())
    .then(data => {
      if (id === requestId) {
        select.setData(data);
      }
    });
}

Использование слишком больших наборов данных

Slim Select не предназначен для работы с десятками тысяч элементов без виртуализации. Передача больших массивов приводит к значительному падению производительности при открытии списка и фильтрации.

Антипаттерн:

select.setData(hugeArrayOfCountries); // 50 000+ элементов

Основные симптомы:

  • задержка открытия dropdown
  • лаги при поиске
  • блокировка основного потока

Рациональная стратегия — серверная фильтрация или пагинация данных.


Неправильная реализация поиска

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

Антипаттерн:

select.search(input => {
  return data.filter(item =>
    item.text.toLowerCase().includes(input.toLowerCase())
  );
});

При быстром вводе это вызывает избыточные вычисления.

Оптимизация через ограничение частоты вызовов:

let timeout;

input.addEventListener('input', (e) => {
  clearTimeout(timeout);

  timeout = setTimeout(() => {
    select.search(e.target.value);
  }, 200);
});

Мутация исходного массива данных

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

Антипаттерн:

const data = [
  { text: 'A', value: 'a' }
];

select.setData(data);

// где-то в другом месте
data.push({ text: 'B', value: 'b' });

Slim Select не отслеживает такие изменения автоматически.

Правильный подход — работа с копиями:

select.setData([...data]);

Игнорирование состояния выбранного значения при обновлении данных

При обновлении списка опций часто теряется текущее выбранное значение, если оно не синхронизируется вручную.

Антипаттерн:

select.setData(newData);

Если value текущего выбора отсутствует в новом списке, состояние сбрасывается без контроля.

Корректная стратегия — явная проверка:

const value = select.getSelected();

select.setData(newData);

if (newData.some(i => i.value === value)) {
  select.setSelected(value);
}

Избыточное создание экземпляров в циклах и рендерах

При работе с шаблонами или повторяющимися компонентами часто возникает ситуация, когда Slim Select инициализируется внутри циклов рендера.

Антипаттерн:

items.forEach(() => {
  new SlimSelect({ select: '.dropdown' });
});

Это приводит к множественным привязкам к одному селектору и конфликтам экземпляров.

Правильная модель — уникальные селекторы или явная привязка к элементу:

document.querySelectorAll('.dropdown').forEach(el => {
  new SlimSelect({ select: el });
});

Нарушение изоляции экземпляров и глобальные ссылки

Сохранение экземпляров Slim Select в глобальных переменных без структуры управления приводит к потере контроля над состоянием компонентов.

Антипаттерн:

window.select = new SlimSelect({ select: '#country' });

В сложных интерфейсах это вызывает:

  • невозможность корректного уничтожения
  • конфликт нескольких экранов
  • утечки памяти при навигации

Рациональная структура — использование локальных контейнеров состояния:

const registry = new Map();

registry.set('country', new SlimSelect({ select: '#country' }));

Отсутствие синхронизации с disabled-состоянием

Изменение атрибута disabled напрямую в DOM без уведомления Slim Select приводит к рассинхронизации интерфейса.

Антипаттерн:

document.querySelector('#country').disabled = true;

Корректный подход требует пересборки состояния компонента или обновления через API-логику:

select.disable();

Игнорирование доступности при кастомизации

Глубокая кастомизация рендера часто приводит к потере семантики ARIA и ухудшению доступности. Особенно это проявляется при переопределении шаблонов опций без учёта роли элементов.

Антипаттерн:

renderOption: (data) => `<div>${data.text}</div>`

Отсутствие роли option и связей с aria-selected нарушает поведение скринридеров.

Корректная модель — сохранение семантики:

renderOption: (data) =>
  `<div role="option" aria-selected="false">${data.text}</div>`

Чрезмерная вложенность кастомных render-функций

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

Антипаттерн:

renderOption: (data) => {
  if (data.group) {
    return `<div class="group">${format(data)}</div>`;
  }

  return `<div>${complexTransform(data)}</div>`;
}

Рекомендуемая архитектура — вынесение логики форматирования:

function formatOption(data) {
  return data.group
    ? groupTemplate(data)
    : itemTemplate(data);
}

Отсутствие контроля повторной привязки событий

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

Симптом:

  • одно действие вызывает несколько запросов
  • событие change срабатывает многократно

Причина — отсутствие destroy() или неправильное управление экземплярами.


Итоговая техническая закономерность

Большинство проблем возникает не из-за самой библиотеки, а из-за отсутствия строгого контроля жизненного цикла экземпляров, мутаций данных и синхронизации состояния между DOM и внутренним представлением. Slim Select требует дисциплины в управлении данными и экземплярами, иначе поведение интерфейса становится недетерминированным.