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

Slim Sel ect представляет собой лёгкую JavaScript-библиотеку для кастомизации <select>-элементов, ориентированную на минимальный вес, высокую производительность и простую интеграцию в современные фронтенд-фреймворки. Slim Sel ect Official Vue является реактивным прогрессивным фреймворком для построения пользовательских интерфейсов. Vue Official

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

Основная концепция:

  • Vue управляет состоянием (data, ref, v-model)
  • Slim Select управляет визуализацией <select>
  • Связка происходит через mounted, upd ated, beforeUnmount

Установка и подключение Slim Select

Slim Select подключается как обычная npm-зависимость:

npm install slim-select

Подключение в компоненте Vue:

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

CSS-стили обязательны, так как именно они формируют внешний вид кастомного селекта.

Инициализация Slim Sel ect в компоненте Vue

В Vue 3 наиболее стабильный способ — использование Composition API и ref для доступа к DOM-элементу.

<template>
  <select ref="select">
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
  </select>
</template>
import { ref, onMounted, onBeforeUnmount } fr om 'vue'
import SlimSelect fr om 'slim-select'

export default {
  setup() {
    const sel ect = ref(null)
    let slim = null

    onMounted(() => {
      slim = new SlimSelect({
        select: sel ect.value
      })
    })

    onBeforeUnmount(() => {
      if (slim) slim.destroy()
    })

    return { select }
  }
}

Ключевой момент заключается в том, что инициализация происходит только после монтирования DOM, иначе Slim Select не сможет найти элемент.

Реактивная синхронизация v-model и Slim Select

В Vue стандартный механизм связывания данных реализуется через v-model, однако Slim Select не знает о реактивности Vue напрямую. Поэтому требуется явная синхронизация состояния.

Односторонняя инициализация значения

<select ref="select" v-model="selected">
  <option value="a">A</option>
  <option value="b">B</option>
</select>
import { ref, watch, onMounted } fr om 'vue'
import SlimSelect fr om 'slim-select'

export default {
  setup() {
    const select = ref(null)
    const selected = ref('a')
    let slim = null

    onMounted(() => {
      slim = new SlimSelect({
        select: select.value,
        events: {
          afterChange: (newVal) => {
            selected.value = newVal[0].value
          }
        }
      })

      slim.se t(selected.value)
    })

    watch(selected, (val) => {
      if (slim) slim.set(val)
    })

    return { select, selected }
  }
}

Здесь используется двусторонняя синхронизация:

  • Slim Select → Vue через afterChange
  • Vue → Slim Select через watch

Работа с массивами значений (multiple select)

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

<select ref="select" multiple>
  <option value="1">One</option>
  <option value="2">Two</option>
  <option value="3">Three</option>
</select>
const selected = ref(['1', '3'])

Синхронизация:

slim = new SlimSelect({
  select: select.value,
  events: {
    afterChange: (values) => {
      selected.value = values.map(v => v.value)
    }
  }
})

watch(selected, (val) => {
  slim.set(val)
})

В мультивыборе Slim Select всегда возвращает массив объектов, даже если изменился один элемент.

Динамическое обновление options

Vue часто работает с данными, полученными с API, поэтому список <option> может изменяться динамически.

Проблема заключается в том, что Slim Select не всегда автоматически отслеживает изменения DOM.

Решение через re-init

watch(options, async () => {
  if (slim) {
    slim.destroy()
  }

  await nextTick()

  slim = new SlimSelect({
    select: select.value
  })
})

Такой подход гарантирует, что:

  • DOM обновлён Vue
  • Slim Select пересоздан на актуальной разметке

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

При работе с API часто требуется загружать данные перед инициализацией.

const options = ref([])

onMounted(async () => {
  const res = await fetch('/api/options')
  options.value = await res.json()

  await nextTick()

  slim = new SlimSelect({
    select: select.value
  })
})

Ключевой момент — инициализация должна происходить после полной отрисовки <option>.

Использование с Composition API vs Options API

Composition API

Предпочтительный вариант:

  • контроль lifecycle hooks
  • удобная работа с ref
  • простая очистка ресурсов

Options API

export default {
  data() {
    return {
      slim: null
    }
  },
  mounted() {
    this.slim = new SlimSelect({
      select: this.$refs.select
    })
  },
  beforeUnmount() {
    this.slim.destroy()
  }
}

Options API требует больше дисциплины при работе с очисткой ресурсов.

Обработка обновлений компонента

Vue может перерисовывать DOM при изменении состояния. Если Slim Select не пересоздаётся, возможна десинхронизация.

Стратегии:

1. Полный пересоздание

watch(data, () => {
  slim.destroy()
  slim = new SlimSelect({ select: select.value })
})

2. Обновление через set

slim.set(newValue)

Этот способ предпочтителен, так как не требует пересоздания DOM-инстанса.

Очистка ресурсов

Игнорирование уничтожения Slim Select приводит к утечкам памяти и дублированию обработчиков событий.

onBeforeUnmount(() => {
  if (slim) {
    slim.destroy()
    slim = null
  }
})

Особенности SSR (Nuxt / Server-Side Rendering)

Slim Select не совместим с SSR напрямую, так как использует document.

Решение — ленивое подключение:

onMounted(async () => {
  const SlimSelect = (await import('slim-select')).default

  slim = new SlimSelect({
    select: select.value
  })
})

Это предотвращает ошибки window is not defined.

Частые проблемы интеграции

1. Несоответствие v-model и UI

Причина: отсутствие синхронизации через watch или afterChange.

2. Пустой select при инициализации

Причина: Slim Select инициализируется до загрузки options.

3. Дублирование экземпляров

Причина: повторная инициализация без destroy().

4. Потеря реактивности

Причина: изменение DOM без уведомления Vue.

Оптимальная архитектура обёртки компонента

Для масштабируемых приложений создаётся абстракция:

  • <BaseSelect />
  • внутри: Slim Select
  • наружу: чистый v-model

Пример логики:

emits: ['update:modelValue']
events: {
  afterChange: (val) => emit('update:modelValue', val[0].value)
}

Такая архитектура изолирует Slim Select от бизнес-логики Vue-приложения и предотвращает распространение побочных эффектов по коду.