Перерисовки в интерфейсных компонентах на основе DOM часто становятся узким местом при работе с большими наборами данных, и Tom Select не является исключением: каждая операция обновления списка опций, значений или состояния контролов может приводить к повторному построению DOM-узлов, пересчёту лэйаута и дорогостоящим repaint/reflow.
Внутренне Tom Select опирается на несколько ключевых слоёв:
Каждое изменение состояния потенциально триггерит цепочку обновлений:
setValue → sync → refreshItems → refreshOptions → renderDropdown
При неоптимальном использовании API эти цепочки становятся причиной избыточных перерисовок.
Одна из самых частых проблем — множественные вызовы установки значения:
select.setValue('a');
select.setValue('b');
select.setValue('c');
Каждый вызов приводит к:
Tom Select позволяет подавлять лишние события:
select.setValue(['a', 'b', 'c'], true);
Флаг silent уменьшает количество побочных эффектов и предотвращает каскадные обновления UI.
Dropdown — самая дорогая часть интерфейса, особенно при большом числе опций.
Метод refreshOptions при каждом изменении может:
При тысячах элементов это приводит к заметной задержке.
Использование конфигурации:
{
maxOptions: 50
}
снижает нагрузку, уменьшая DOM-дерево до фиксированного размера, что критично для производительности.
Функции renderOption и renderItem вызываются при каждом обновлении состояния.
render: {
option: function(data, escape) {
return `<div>${escape(data.text)}</div>`;
}
}
При каждом refreshOptions создаются новые HTML-строки, даже если данные не изменились.
Ключевой принцип — детерминированный рендер без побочных эффектов:
Дополнительно можно кэшировать результат:
const cache = new Map();
render: {
option: function(data, escape) {
if (cache.has(data.value)) return cache.get(data.value);
const html = `<div>${escape(data.text)}</div>`;
cache.set(data.value, html);
return html;
}
}
Поиск через input вызывает частые обновления dropdown:
input event → load → refreshOptions → render
Без ограничения частоты запросов возникает серия перерисовок.
function debounce(fn, delay) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
Применение:
select.on('type', debounce(function(query) {
select.load(function(callback) {
fetch('/api?q=' + query)
.then(r => r.json())
.then(callback);
});
}, 200));
Это снижает частоту refreshOptions и уменьшает DOM churn.
refreshOptions часто вызывается при:
При каждом вызове происходит:
Если данные статичны, фильтрацию можно вынести наружу:
const precomputed = data.map(item => ({
...item,
searchIndex: item.text.toLowerCase()
}));
И отключить лишнюю логику сортировки.
Частая ошибка — уничтожение и создание нового Tom Select при каждом изменении данных.
select.destroy();
select = new TomSelect('#select', config);
Это приводит к:
Гораздо дешевле обновлять данные:
select.clearOptions();
select.addOptions(newOptions);
select.refreshOptions(false);
При тысячах элементов критично:
Чем проще шаблон option, тем быстрее repaint:
render: {
option: (data, escape) => `<div>${escape(data.text)}</div>`
}
loadCallback может вызываться многократно при быстром вводе.
Проблема: ответы приходят не по порядку.
let requestId = 0;
select.load(function(query, callback) {
const id = ++requestId;
fetch('/api?q=' + query)
.then(r => r.json())
.then(data => {
if (id !== requestId) return;
callback(data);
});
});
Это предотвращает лишние refreshOptions от устаревших запросов.
Каждое открытие dropdown может вызывать пересборку списка.
Оптимизация:
select.setValue(value, true);
select.open();
Input внутри control также участвует в синхронизации состояния.
При массовых обновлениях:
select.setValue(values, true);
select.blur();
можно снизить количество перерасчётов фокуса и layout shifts.
Некоторые операции лучше выносить в следующий кадр:
function scheduleUpdate(fn) {
requestAnimationFrame(() => fn());
}
Применение:
Tom Select не имеет встроенного виртуального списка, поэтому при больших данных критично:
Фильтрация на клиенте должна рассматриваться как исключение, а не основная стратегия.
При интенсивных обновлениях важно избегать цепных реакций:
Подход — разделение потоков:
Чёткое разделение уменьшает количество каскадных перерисовок и стабилизирует поведение компонента при высокой частоте изменений.