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

Интеграция Choices.js в Svelte опирается на жизненный цикл компонента и работу с DOM-элементами через bind:this. Поскольку Svelte выполняет реактивное обновление состояния, а Choices.js напрямую управляет DOM, ключевой задачей становится изоляция управления библиотекой в пределах onMount и корректное освобождение ресурсов при размонтировании компонента.

Базовая структура подключения начинается с создания ссылки на <select> или <input> элемент:

<script>
  import { onMount, onDestroy } from 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  const options = [
    { value: 'js', label: 'JavaScript' },
    { value: 'ts', label: 'TypeScript' },
    { value: 'py', label: 'Python' }
  ];
</script>

<sel ect bind:this={selectElement}>
  <option value="">Выбор языка</option>
  {#each options as opt}
    <option value={opt.value}>{opt.label}</option>
  {/each}
</select>

Инициализация Choices.js выполняется после монтирования компонента, когда DOM-узел уже существует:

<script>
  import { onMount, onDestroy } fr om 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  const options = [
    { value: 'js', label: 'JavaScript' },
    { value: 'ts', label: 'TypeScript' },
    { value: 'py', label: 'Python' }
  ];

  onMount(() => {
    choicesInstance = new Choices(selectElement, {
      searchEnabled: true,
      shouldSort: false,
      itemSelectText: '',
      placeholderValue: 'Выберите язык'
    });
  });

  onDestroy(() => {
    if (choicesInstance) {
      choicesInstance.destroy();
    }
  });
</script>

Реактивные данные и синхронизация состояния

Svelte управляет состоянием через реактивные переменные, тогда как Choices.js хранит собственное внутреннее состояние. Для синхронизации используется обновление через API экземпляра.

Реактивное изменение списка опций:

<script>
  import { onMount } from 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  let languages = [
    { value: 'js', label: 'JavaScript' },
    { value: 'ts', label: 'TypeScript' }
  ];

  onMount(() => {
    choicesInstance = new Choices(selectElement, {
      shouldSort: false
    });
  });

  function addLanguage() {
    languages = [
      ...languages,
      { value: 'py', label: 'Python' }
    ];

    choicesInstance.clearChoices();
    choicesInstance.setChoices(languages, 'value', 'label', true);
  }
</script>

<sel ect bind:this={selectElement}>
  {#each languages as lang}
    <option value={lang.value}>{lang.label}</option>
  {/each}
</select>

<button on:click={addLanguage}>Добавить язык</button>

Метод setChoices с четвёртым аргументом true позволяет полностью перезаписать список, обеспечивая согласованность между состоянием Svelte и внутренним состоянием Choices.js.

Работа с выбранными значениями

Choices.js не заменяет механизм привязки Svelte, поэтому для отслеживания выбранного значения используется обработка событий DOM или подписка на события библиотеки.

Обработка изменения выбора:

<script>
  import { onMount } fr om 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  let selectedValue = '';

  onMount(() => {
    choicesInstance = new Choices(selectElement, {
      shouldSort: false
    });

    selectElement.addEventListener('change', (event) => {
      selectedValue = event.target.value;
    });
  });
</script>

<p>Выбрано: {selectedValue}</p>

<sel ect bind:this={selectElement}>
  <option value="js">JavaScript</option>
  <option value="ts">TypeScript</option>
</select>

Альтернативный подход использует API Choices.js:

choicesInstance.passedElement.element.addEventListener(
  'change',
  (event) => {
    selectedValue = event.detail.value;
  }
);

Использование с множественным выбором

При включении removeItemButton и maxItemCount компонент Svelte остаётся источником данных, а Choices.js отвечает за UI-слой.

<script>
  import { onMount } fr om 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  let selected = [];

  onMount(() => {
    choicesInstance = new Choices(selectElement, {
      removeItemButton: true,
      maxItemCount: 3,
      shouldSort: false
    });

    selectElement.addEventListener('change', (e) => {
      selected = Array.from(selectElement.selectedOptions).map(
        (opt) => opt.value
      );
    });
  });
</script>

<sel ect bind:this={selectElement} multiple>
  <option value="js">JavaScript</option>
  <option value="ts">TypeScript</option>
  <option value="py">Python</option>
</select>

<p>Выбранные значения: {selected.join(', ')}</p>

Динамическое обновление данных через Svelte store

При использовании writable store синхронизация становится централизованной. Choices.js обновляется при изменении store.

<script>
  import { onMount } fr om 'svelte';
  import { writable } from 'svelte/store';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  const optionsStore = writable([
    { value: 'js', label: 'JavaScript' }
  ]);

  let options = [];

  optionsStore.subscribe((value) => {
    options = value;

    if (choicesInstance) {
      choicesInstance.setChoices(options, 'value', 'label', true);
    }
  });

  onMount(() => {
    choicesInstance = new Choices(selectElement, {
      shouldSort: false
    });
  });

  function addOption() {
    optionsStore.update((prev) => [
      ...prev,
      { value: 'ts', label: 'TypeScript' }
    ]);
  }
</script>

<sel ect bind:this={selectElement}>
  {#each options as opt}
    <option value={opt.value}>{opt.label}</option>
  {/each}
</select>

<button on:click={addOption}>Добавить</button>

Интеграция с асинхронной загрузкой данных

Choices.js поддерживает ленивую подгрузку через setChoices, что удобно при запросах к API.

<script>
  import { onMount } fr om 'svelte';
  import Choices from 'choices.js';

  let selectElement;
  let choicesInstance;

  async function fetchOptions() {
    return [
      { value: 'ru', label: 'Russian' },
      { value: 'en', label: 'English' },
      { value: 'de', label: 'German' }
    ];
  }

  onMount(async () => {
    choicesInstance = new Choices(selectElement, {
      searchEnabled: true
    });

    const data = await fetchOptions();
    choicesInstance.setChoices(data, 'value', 'label', true);
  });
</script>

<sel ect bind:this={selectElement}></select>

Управление жизненным циклом и очистка состояния

Особенность интеграции заключается в необходимости строгого удаления экземпляра при размонтировании компонента. Игнорирование destroy() приводит к утечкам памяти и дублированию обработчиков событий.

onDestroy(() => {
  if (choicesInstance) {
    choicesInstance.destroy();
    choicesInstance = null;
  }
});

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

Работа с кастомными шаблонами отображения

Choices.js позволяет переопределять отображение элементов через конфигурацию callbackOnCreateTemplates, что может быть адаптировано под Svelte-компоненты, если требуется унификация UI-стиля.

choicesInstance = new Choices(selectElement, {
  callbackOnCreateTemplates: function (template) {
    return {
      item: (classNames, data) => {
        return template(`
          <div class="${classNames.item} ${data.highlighted
            ? classNames.highlightedState
            : classNames.itemSelectable}">
            ${data.label}
          </div>
        `);
      }
    };
  }
});

Особенности SSR и SvelteKit

При использовании SvelteKit и серверного рендеринга необходимо учитывать, что Choices.js работает исключительно в браузере. Любая инициализация должна быть ограничена onMount, который не выполняется на сервере.

<script>
  import { browser } fr om '$app/environment';
  import { onMount } from 'svelte';
  import Choices from 'choices.js';

  let selectElement;

  onMount(() => {
    if (!browser) return;

    const instance = new Choices(selectElement, {
      shouldSort: false
    });

    return () => instance.destroy();
  });
</script>

Такой подход исключает ошибки гидратации и обращения к window или document на сервере.

Синхронизация внешнего состояния и UI-слоя

При сложных интерфейсах Choices.js выступает как слой представления, тогда как Svelte хранит истинное состояние. Любое изменение должно проходить через единый источник данных, иначе возникает рассинхронизация DOM и логики приложения.

Модель взаимодействия:

  • Svelte store → источник данных
  • Choices.js → UI слой выбора
  • события DOM → мост синхронизации

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