Отложенная инициализация применяется в случаях, когда элемент управления Choices.js не может быть создан сразу при загрузке страницы или когда данные для него появляются асинхронно. Такой подход позволяет избежать ошибок работы с DOM, снизить нагрузку при старте приложения и гибко управлять жизненным циклом экземпляра.
Одной из основных причин отложенной инициализации является отсутствие данных на момент загрузки страницы. Например, список опций может приходить с сервера после выполнения HTTP-запроса.
Типичный сценарий включает ожидание двух условий:
<select> уже присутствует в DOM;import Choices from 'choices.js';
const sel ect = document.querySelector('#country-select');
fetch('/api/countries')
.then(response => response.json())
.then(data => {
const choices = new Choices(select, {
searchEnabled: true,
shouldSort: false
});
choices.setChoices(
data.map(item => ({
value: item.code,
label: item.name
})),
'value',
'label',
true
);
});
Ключевая особенность данного подхода заключается в том, что экземпляр Choices создаётся только после получения данных, что исключает необходимость последующего пересоздания структуры.
Во многих случаях элемент существует в разметке, но скрипт
выполняется раньше, чем DOM полностью построен. Это особенно актуально
при подключении JavaScript в <head> без
defer.
Отложенная инициализация через событие DOMContentLoaded
решает эту проблему.
import Choices fr om 'choices.js';
document.addEventListener('DOMContentLoaded', () => {
const element = document.querySelector('#city-select');
const choices = new Choices(element, {
removeItemButton: true,
searchPlaceholderValue: 'Поиск...'
});
});
Такой подход гарантирует наличие элемента в DOM и предотвращает
попытки инициализации на null.
В интерфейсах с большим количеством форм или вкладок инициализация всех компонентов сразу может быть избыточной. Choices.js можно подключать только при первом взаимодействии пользователя.
import Choices from 'choices.js';
let cityChoices = null;
const citySelect = document.querySelector('#city-select');
function initCitySelect() {
if (cityChoices) return;
cityChoices = new Choices(citySelect, {
searchEnabled: true
});
}
citySelect.addEventListener('focus', initCitySelect, { once: true });
В этом варианте экземпляр создаётся только при первом фокусе на элементе. Это снижает начальную нагрузку на страницу, особенно при наличии десятков селектов.
В приложениях с динамической отрисовкой (React-подобные или собственные SPA) DOM может пересоздаваться. В таких условиях отложенная инициализация должна учитывать жизненный цикл компонента.
import Choices from 'choices.js';
function mountSelect(container) {
const sel ect = container.querySelector('select');
const instance = new Choices(select, {
shouldSort: false
});
return instance;
}
let instance = null;
function renderView() {
const container = document.querySelector('#view');
container.innerHTML = `
<select id="tags">
<option value="js">JavaScript</option>
<option value="py">Python</option>
</select>
`;
instance = mountSelect(container);
}
При каждом повторном рендере необходимо учитывать уничтожение предыдущего экземпляра:
if (instance) {
instance.destroy();
instance = null;
}
Игнорирование этого шага приводит к утечкам памяти и дублированию DOM-структур.
Choices.js не отслеживает появление новых элементов автоматически.
Если <select> добавляется в DOM после загрузки
страницы, требуется явная инициализация.
import Choices fr om 'choices.js';
function addSelect() {
const sel ect = document.createElement('select');
select.id = 'dynamic-select';
select.innerHTML = `
<option value="1">Первый</option>
<option value="2">Второй</option>
`;
document.body.appendChild(select);
new Choices(select, {
searchEnabled: false
});
}
В этом случае важен порядок операций: сначала вставка в DOM, затем инициализация.
Иногда Choices создаётся пустым, а данные подгружаются позже. Тогда
используется обновление через setChoices.
import Choices fr om 'choices.js';
const sel ect = document.querySelector('#product-select');
const choices = new Choices(select, {
shouldSort: false
});
async function loadProducts() {
const res = await fetch('/api/products');
const data = await res.json();
choices.setChoices(
data.map(p => ({
value: p.id,
label: p.title
})),
'value',
'label',
true
);
}
loadProducts();
Такой вариант удобен, когда визуальная структура должна быть доступна сразу, но данные загружаются постепенно.
В сложных интерфейсах, где элементы могут появляться из сторонних скриптов, можно использовать наблюдение за DOM.
import Choices fr om 'choices.js';
const observer = new MutationObserver(() => {
const sel ect = document.querySelector('#async-select');
if (select && !select.dataset.initialized) {
select.dataset.initialized = 'true';
new Choices(select, {
searchEnabled: true
});
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
Маркер dataset.initialized предотвращает повторную
инициализацию одного и того же элемента.
Отложенная инициализация требует строгого контроля состояния экземпляра Choices.js. Основные операции включают:
let instance = null;
function init(select) {
if (instance) {
instance.destroy();
}
instance = new Choices(select, {
removeItemButton: true
});
}
Такой шаблон применяется в случаях повторного входа в экран или динамического обновления формы.
Одной из распространённых проблем является повторная инициализация
одного и того же элемента без вызова destroy. Это приводит
к:
Другой частой ошибкой является попытка инициализации до появления элемента:
const el = document.querySelector('#select');
new Choices(el); // el может быть null
Корректный подход требует проверки существования элемента или использования событий загрузки.
В реальных приложениях часто сочетаются несколько подходов одновременно: ленивое создание, асинхронная загрузка данных и динамическое добавление DOM-элементов.
import Choices fr om 'choices.js';
let choicesInstance = null;
async function ensureInit() {
const select = document.querySelector('#combo-select');
if (!select) return;
if (!choicesInstance) {
choicesInstance = new Choices(select, {
searchEnabled: true
});
const res = await fetch('/api/items');
const items = await res.json();
choicesInstance.setChoices(
items.map(i => ({
value: i.id,
label: i.name
})),
'value',
'label',
true
);
}
}
Такой вариант объединяет контроль инициализации и загрузку данных в одном потоке, минимизируя количество пересозданий интерфейса.