Интеграция с Svelte

Интеграция Slim Sel ect с Svelte опирается на взаимодействие императивной библиотеки управления DOM и реактивного подхода фреймворка. Slim Sel ect модифицирует <select> элементы напрямую, тогда как Svelte управляет DOM через компиляцию реактивных выражений. Это приводит к необходимости чёткого разделения зон ответственности: Svelte управляет состоянием данных, Slim Sel ect — визуализацией и UX слоями селекта.

Ключевой момент интеграции заключается в инициализации Slim Sel ect после монтирования DOM-элемента и последующем корректном обновлении экземпляра при изменении данных.

Установка Slim Sel ect в проект Svelte

Библиотека устанавливается стандартным способом через npm:

npm install slim-select

Подключение стилей является обязательным, так как Slim Sel ect не предоставляет стили по умолчанию в рамках JS-логики:

import 'slim-select/dist/slimselect.css';
import SlimSelect fr om 'slim-select';

В Svelte-компоненте подключение выполняется внутри <script> блока.

Базовая инициализация в Svelte

Основной механизм интеграции строится на использовании onMount, поскольку DOM-элемент <select> должен существовать до создания экземпляра Slim Sel ect.

<script>
  import { onMount, onDestroy } fr om 'svelte';
  import SlimSelect fr om 'slim-select';
  import 'slim-select/dist/slimselect.css';

  let selectEl;
  let slimSelect;

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl
    });
  });

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

<sel ect bind:this={selectEl}>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</select>

Механика bind:this обеспечивает прямую ссылку на DOM-узел, что является критичным для библиотек, работающих вне виртуального DOM.

Управление реактивными данными

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

Обновление списка опций

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

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;

  let options = [
    { value: '1', text: 'Alpha' },
    { value: '2', text: 'Beta' }
  ];

  function rebuild() {
    if (slimSelect) {
      slimSelect.destroy();
    }

    slimSelect = new SlimSelect({
      select: selectEl
    });
  }

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl
    });
  });

  $: options, rebuild();
</script>

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

Такой подход гарантирует консистентность DOM между Svelte и Slim Sel ect.

Обновление данных без пересоздания

Slim Select предоставляет API для частичного обновления, что снижает нагрузку на DOM.

slimSelect.setData([
  { text: 'Gamma', value: '3' },
  { text: 'Delta', value: '4' }
]);

В Svelte это удобно комбинировать с реактивными блоками:

$: if (slimSelect) {
  slimSelect.setData(options);
}

Важно учитывать, что setData полностью заменяет набор опций, поэтому предварительная фильтрация выполняется на стороне состояния.

Управление выбранными значениями

Svelte обычно хранит состояние выбора в переменной, но Slim Select управляет этим состоянием отдельно. Для синхронизации используется двустороннее обновление.

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;

  let selected = '2';

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl,
      events: {
        afterChange: (newVal) => {
          selected = newVal[0]?.value;
        }
      }
    });
  });

  $: if (slimSelect && selected) {
    slimSelect.set(selected);
  }
</script>

<sel ect bind:this={selectEl}>
  <option value="1">One</option>
  <option value="2">Two</option>
</select>

Здесь происходит явная синхронизация реактивного состояния Svelte с внутренним состоянием Slim Sel ect.

Работа с множественным выбором

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

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;

  let selected = ['1', '3'];

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl,
      settings: {
        placeholderText: 'Выбор значений'
      },
      events: {
        afterChange: (values) => {
          selected = values.map(v => v.value);
        }
      }
    });
  });

  $: if (slimSelect) {
    slimSelect.set(selected);
  }
</script>

<sel ect bind:this={selectEl} multiple>
  <option value="1">First</option>
  <option value="2">Second</option>
  <option value="3">Third</option>
</select>

Важный аспект — всегда хранить массив значений как источник истины в Svelte-стороне.

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

При работе с API Slim Sel ect часто используется как слой отображения, а данные подгружаются динамически.

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;
  let loading = false;

  async function fetchOptions() {
    loading = true;

    const res = await fetch('/api/options');
    const data = await res.json();

    loading = false;

    if (slimSelect) {
      slimSelect.setData(
        data.map(item => ({
          text: item.name,
          value: item.id
        }))
      );
    }
  }

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl
    });

    fetchOptions();
  });
</script>

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

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

Динамическое обновление при реактивных изменениях

Svelte позволяет автоматически отслеживать изменения состояния через реактивные блоки, однако Slim Sel ect требует контроля за моментом обновления.

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;

  let filter = '';
  let allOptions = [
    { text: 'Apple', value: 'a' },
    { text: 'Banana', value: 'b' },
    { text: 'Cherry', value: 'c' }
  ];

  $: filtered = allOptions.filter(o =>
    o.text.toLowerCase().includes(filter.toLowerCase())
  );

  $: if (slimSelect) {
    slimSelect.setData(filtered);
  }

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl
    });
  });
</script>

<input bind:value={filter} placeholder="filter" />
<sel ect bind:this={selectEl}></select>

Фильтрация полностью управляется реактивной системой Svelte, Slim Sel ect лишь отображает результат.

Очистка и уничтожение экземпляра

Важной частью интеграции является корректное освобождение ресурсов. Slim Select создаёт собственные обработчики событий и DOM-обёртки.

<script>
  import { onDestroy } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let slimSelect;

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

Игнорирование этого шага приводит к утечкам памяти при частых монтированиях компонентов.

Управление несколькими экземплярами

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

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selects = [];
  let instances = [];

  onMount(() => {
    instances = selects.map(el => new SlimSelect({ select: el }));
  });
</script>

<sel ect bind:this={selects[0]}>
  <option value="1">A</option>
</select>

<sel ect bind:this={selects[1]}>
  <option value="2">B</option>
</select>

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

Особенности SSR в Svelte

При использовании SvelteKit или SSR-рендеринга важно учитывать отсутствие DOM на сервере. Slim Sel ect не может быть инициализирован до гидратации.

Стандартная схема:

<script>
  import { browser } fr om '$app/environment';
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  let selectEl;
  let slimSelect;

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

    slimSelect = new SlimSelect({
      select: selectEl
    });
  });
</script>

Инициализация строго ограничивается клиентской средой.

Кастомизация поведения через события

Slim Sel ect предоставляет события жизненного цикла, которые удобно интегрировать с реактивной моделью Svelte.

<script>
  import { createEventDispatcher, onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';

  const dispatch = createEventDispatcher();

  let selectEl;

  onMount(() => {
    new SlimSelect({
      select: selectEl,
      events: {
        afterChange: (values) => {
          dispatch('change', values);
        }
      }
    });
  });
</script>

<sel ect bind:this={selectEl}>
  <option value="1">One</option>
</select>

Такой подход позволяет инкапсулировать Slim Sel ect внутри компонента и передавать события наружу в Svelte-стиль.

Синхронизация с Svelte stores

При использовании глобального состояния Svelte stores интеграция становится более структурированной.

<script>
  import { onMount } fr om 'svelte';
  import SlimSelect fr om 'slim-select';
  import { writable } fr om 'svelte/store';

  export const selectedStore = writable([]);

  let selectEl;
  let slimSelect;

  onMount(() => {
    slimSelect = new SlimSelect({
      select: selectEl,
      events: {
        afterChange: (values) => {
          selectedStore.set(values.map(v => v.value));
        }
      }
    });
  });

  selectedStore.subscribe(values => {
    if (slimSelect) {
      slimSelect.set(values);
    }
  });
</script>

<select bind:this={selectEl} multiple>
  <option value="1">One</option>
  <option value="2">Two</option>
</select>

Store выступает единым источником состояния для всей прикладной логики.

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

При интенсивных изменениях данных важным становится контроль частоты вызовов setData. Частые пересоздания вызывают деградацию производительности.

Применяется буферизация обновлений через реактивные блоки:

let pending;

$: {
  clearTimeout(pending);

  pending = setTimeout(() => {
    if (slimSelect) {
      slimSelect.setData(filtered);
    }
  }, 100);
}

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

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

При интеграции с асинхронными источниками данных возможны состояния, когда Slim Select уже уничтожен, но обновление данных всё ещё выполняется. Защита строится через проверки экземпляра:

if (slimSelect && !slimSelect.destroyed) {
  slimSelect.setData(data);
}

Дополнительно используется обнуление ссылки при уничтожении:

onDestroy(() => {
  slimSelect?.destroy();
  slimSelect = null;
});