Slim Sel ect представляет собой лёгкую JavaScript-библиотеку для
кастомизации <select>-элементов, ориентированную на
минимальный вес, высокую производительность и простую интеграцию в
современные фронтенд-фреймворки. Slim Sel ect
Official Vue является реактивным прогрессивным фреймворком для
построения пользовательских интерфейсов. Vue Official
Интеграция Slim Select с Vue строится вокруг жизненного цикла компонента и синхронизации состояния между DOM и реактивной моделью. Поскольку Slim Select напрямую работает с DOM-узлами, а Vue оперирует виртуальным DOM, ключевая задача заключается в корректной инициализации и уничтожении экземпляра библиотеки в нужные моменты.
Основная концепция:
data, ref,
v-model)<select>mounted, upd ated,
beforeUnmountSlim Select подключается как обычная npm-зависимость:
npm install slim-select
Подключение в компоненте Vue:
import SlimSelect fr om 'slim-select'
import 'slim-select/dist/slimselect.css'
CSS-стили обязательны, так как именно они формируют внешний вид кастомного селекта.
В 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 не сможет найти элемент.
В 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 }
}
}
Здесь используется двусторонняя синхронизация:
afterChangewatchSlim 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 всегда возвращает массив объектов, даже если изменился один элемент.
Vue часто работает с данными, полученными с API, поэтому список
<option> может изменяться динамически.
Проблема заключается в том, что Slim Select не всегда автоматически отслеживает изменения DOM.
watch(options, async () => {
if (slim) {
slim.destroy()
}
await nextTick()
slim = new SlimSelect({
select: select.value
})
})
Такой подход гарантирует, что:
При работе с 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>.
Предпочтительный вариант:
refexport default {
data() {
return {
slim: null
}
},
mounted() {
this.slim = new SlimSelect({
select: this.$refs.select
})
},
beforeUnmount() {
this.slim.destroy()
}
}
Options API требует больше дисциплины при работе с очисткой ресурсов.
Vue может перерисовывать DOM при изменении состояния. Если Slim Select не пересоздаётся, возможна десинхронизация.
Стратегии:
watch(data, () => {
slim.destroy()
slim = new SlimSelect({ select: select.value })
})
slim.set(newValue)
Этот способ предпочтителен, так как не требует пересоздания DOM-инстанса.
Игнорирование уничтожения Slim Select приводит к утечкам памяти и дублированию обработчиков событий.
onBeforeUnmount(() => {
if (slim) {
slim.destroy()
slim = null
}
})
Slim Select не совместим с SSR напрямую, так как использует
document.
Решение — ленивое подключение:
onMounted(async () => {
const SlimSelect = (await import('slim-select')).default
slim = new SlimSelect({
select: select.value
})
})
Это предотвращает ошибки window is not defined.
Причина: отсутствие синхронизации через watch или
afterChange.
Причина: Slim Select инициализируется до загрузки options.
Причина: повторная инициализация без destroy().
Причина: изменение DOM без уведомления Vue.
Для масштабируемых приложений создаётся абстракция:
<BaseSelect />v-modelПример логики:
emits: ['update:modelValue']
events: {
afterChange: (val) => emit('update:modelValue', val[0].value)
}
Такая архитектура изолирует Slim Select от бизнес-логики Vue-приложения и предотвращает распространение побочных эффектов по коду.