Svelte компонент

Архитектурная модель интеграции

Интеграция сторонних DOM-ориентированных библиотек в Svelte требует понимания жизненного цикла компонентов и принципа реактивности. Tom Sel ect работает поверх нативного <select> или <input> и самостоятельно управляет DOM, что делает его несовместимым с прямым декларативным подходом Svelte без адаптационного слоя.

Основная задача интеграции заключается в синхронизации трёх уровней состояния:

  • состояние Svelte (reactive variables)
  • состояние DOM (native select/input)
  • внутреннее состояние Tom Select

Ключевой принцип — единый источник истины в Svelte, а библиотека выступает как UI-слой.


Базовый Svelte-компонент-обёртка

Структура компонента обычно строится вокруг onMount, onDestroy и ссылок на DOM-элемент.

<script>
  import { onMount, onDestroy } fr om "svelte";
  import TomSelect from "tom-select";

  export let options = [];
  export let value = null;
  export let placeholder = "Sel ect...";

  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl, {
      options,
      placeholder,
      valueField: "value",
      labelField: "label",
      searchField: "label"
    });

    if (value) {
      instance.setValue(value);
    }

    instance.on("change", (val) => {
      value = val;
    });
  });

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

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

Синхронизация реактивности Svelte

Svelte обновляет переменные реактивно, но Tom Sel ect не знает о внешних изменениях состояния. Поэтому требуется явная синхронизация через afterUpdate.

<script>
  import { onMount, onDestroy, afterUpdate } fr om "svelte";
  import TomSelect fr om "tom-select";

  export let value;
  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl, {});

    instance.on("change", (val) => {
      value = val;
    });
  });

  afterUpdate(() => {
    if (instance && instance.getValue() !== value) {
      instance.setValue(value);
    }
  });

  onDestroy(() => instance?.destroy());
</script>

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

Двусторонняя привязка и предотвращение циклов

Основная сложность при работе с Tom Sel ect внутри Svelte — это циклические обновления:

  • изменение value → обновление Tom Select
  • изменение Tom Sel ect → обновление value

Решение заключается в флаговой синхронизации:

<script>
  import { onMount, onDestroy, afterUpdate } fr om "svelte";
  import TomSelect fr om "tom-select";

  export let value;

  let selectEl;
  let instance;
  let internalUpdate = false;

  onMount(() => {
    instance = new TomSelect(selectEl);

    instance.on("change", (val) => {
      internalUpdate = true;
      value = val;
      internalUpdate = false;
    });
  });

  afterUpdate(() => {
    if (!instance || internalUpdate) return;

    if (instance.getValue() !== value) {
      instance.setValue(value);
    }
  });

  onDestroy(() => instance?.destroy());
</script>

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

Работа с динамическими опциями

Tom Sel ect поддерживает динамическое изменение списка опций, однако в Svelte это требует явного пересоздания или обновления данных через API экземпляра.

<script>
  import { onMount, afterUpdate } fr om "svelte";
  import TomSelect fr om "tom-select";

  export let options = [];

  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl, {
      options
    });
  });

  afterUpdate(() => {
    if (!instance) return;

    instance.clearOptions();
    instance.addOptions(options);
    instance.refreshOptions(false);
  });
</script>

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

Асинхронная загрузка данных

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

<script>
  import { onMount } fr om "svelte";
  import TomSelect fr om "tom-select";

  let selectEl;
  let instance;

  async function loadOptions(query) {
    const res = await fetch(`/api/search?q=${query}`);
    const data = await res.json();

    instance.clearOptions();
    instance.addOptions(data);
    instance.refreshOptions(false);
  }

  onMount(() => {
    instance = new TomSelect(selectEl, {
      load: (query, callback) => {
        loadOptions(query).then(() => callback());
      }
    });
  });
</script>

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

Инкапсуляция в переиспользуемый Svelte-компонент

Компонент-обёртка позволяет стандартизировать использование Tom Sel ect во всём приложении.

<script>
  import { onMount, onDestroy, createEventDispatcher } fr om "svelte";
  import TomSelect fr om "tom-select";

  export let options = [];
  export let value = null;
  export let settings = {};

  const dispatch = createEventDispatcher();

  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl, {
      ...settings,
      options
    });

    instance.setValue(value);

    instance.on("change", (val) => {
      value = val;
      dispatch("change", val);
    });
  });

  onDestroy(() => instance?.destroy());
</script>

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

Поддержка множественного выбора

Tom Sel ect изначально поддерживает multi-select режим, но в Svelte важно правильно синхронизировать массив значений.

<script>
  import { onMount } fr om "svelte";
  import TomSelect fr om "tom-select";

  export let value = [];

  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl, {
      plugins: ["remove_button"],
      maxItems: null
    });

    instance.on("change", (val) => {
      value = val;
    });

    instance.setValue(value);
  });
</script>

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

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

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

// store.js
import { writable } fr om "svelte/store";

export const selectedValues = writable([]);
<script>
  import { onMount } fr om "svelte";
  import TomSelect from "tom-select";
  import { selectedValues } from "./store";

  let selectEl;
  let instance;

  onMount(() => {
    instance = new TomSelect(selectEl);

    instance.on("change", (val) => {
      selectedValues.set(val);
    });

    selectedValues.subscribe((val) => {
      instance.setValue(val);
    });
  });
</script>

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

Очистка и предотвращение утечек памяти

Корректное уничтожение экземпляра Tom Select критично при частом маунте/анмаунте компонентов.

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

Дополнительно важно учитывать:

  • удаление event listeners
  • остановку async загрузок
  • сброс ссылок на DOM

Типизация и безопасность (TypeScript)

При использовании TypeScript повышается надёжность интеграции.

import TomSelect fr om "tom-select";

let instance: TomSelect | null = null;
let selectEl: HTMLSelectElement;

Типизация особенно важна при работе с динамическими опциями и пользовательскими плагинами Tom Sel ect.


Переиспользуемые паттерны интеграции

Для крупных проектов выделяются устойчивые шаблоны:

  • Wrapper Component Pattern
  • Controlled Component Pattern
  • Store-driven synchronization
  • Lazy initialization
  • Deferred hydration (для SSR)

Каждый из них снижает связанность между Svelte и Tom Select и упрощает масштабирование интерфейса.


Обработка SSR-ограничений

SvelteKit и SSR требуют инициализации только на клиенте:

import { browser } fr om "$app/environment";

onMount(() => {
  if (!browser) return;
  instance = new TomSelect(selectEl);
});

Tom Select не может быть инициализирован на сервере из-за зависимости от DOM.


Производительность и оптимизация

При больших списках ключевыми становятся:

  • виртуализация опций (через внешний слой)
  • дебаунс поиска
  • минимизация пересоздания экземпляра
  • использование refreshOptions(false) вместо полного rebuild

Оптимизация взаимодействия с Tom Select особенно важна в формах с высокой динамикой и частыми обновлениями состояния.