Поведение автодополнения в браузерных интерфейсах напрямую зависит от того, как и когда обновляется источник данных. В случае Awesomplete стратегия обновления списка становится ключевым элементом архитектуры, особенно при работе с динамическими данными, API-запросами и пользовательским вводом, изменяющим контекст поиска.
В основе Awesomplete лежит свойство list, которое
определяет текущий набор предложений. Это может быть массив строк,
массив объектов или функция-фильтр. Самый простой способ обновления —
полная замена списка:
const input = document.querySelector("#search");
const awesomplete = new Awesomplete(input, {
list: ["apple", "banana", "cherry"]
});
// Полная замена списка
awesomplete.list = ["apricot", "blueberry", "coconut"];
При таком подходе компонент пересчитывает внутренние индексы и использует новый набор данных при следующем открытии выпадающего списка. Важно учитывать, что активное состояние (открытое меню) при этом не всегда обновляется автоматически.
Типичная стратегия — обновление списка при каждом изменении input. Это позволяет адаптировать предложения под текущий ввод пользователя.
input.addEventListener("input", () => {
awesomplete.list = fetchLocalSuggestions(input.value);
});
Функция fetchLocalSuggestions может фильтровать
локальный массив или возвращать заранее подготовленные данные. Такой
подход подходит для небольших объемов данных, где нет необходимости в
асинхронной загрузке.
При работе с серверными источниками данных стратегия обновления усложняется. Основной риск — гонка запросов, когда более ранний ответ приходит позже и перезаписывает актуальные данные.
let requestId = 0;
input.addEventListener("input", async () => {
const currentRequest = ++requestId;
const query = input.value;
const response = await fetch(`/api/suggest?q=${query}`);
const data = await response.json();
if (currentRequest !== requestId) return;
awesomplete.list = data.results;
});
Такой механизм гарантирует, что в список попадут только актуальные данные, соответствующие последнему вводу пользователя.
Частое обновление списка приводит к лишним сетевым запросам и нагрузке на UI. Использование debounce снижает количество вызовов обновления:
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const updateSuggestions = debounce(async (value) => {
const res = await fetch(`/api/suggest?q=${value}`);
const data = await res.json();
awesomplete.list = data.results;
}, 300);
input.addEventListener("input", (e) => {
updateSuggestions(e.target.value);
});
Debounce позволяет объединить серию быстрых изменений ввода в один запрос, снижая вероятность устаревших данных и улучшая производительность.
Полная замена списка не всегда оптимальна. В некоторых сценариях требуется частичное обновление: добавление, удаление или модификация элементов без пересоздания всей структуры.
function addItem(item) {
awesomplete.list = [...awesomplete.list, item];
}
function removeItem(item) {
awesomplete.list = awesomplete.list.filter(x => x !== item);
}
Этот подход полезен при локальном кэшировании или при работе с пользовательскими данными, где список изменяется постепенно.
При использовании централизованного состояния (например, store или reactive state) список должен быть производным от состояния, а не изменяться напрямую:
function updateState(newState) {
state.suggestions = newState;
awesomplete.list = state.suggestions;
}
Такой подход упрощает контроль над источником данных и снижает вероятность рассинхронизации интерфейса.
При частом обновлении списка важно учитывать поведение UI. Перезапись списка может закрывать выпадающее меню или сбрасывать выделение.
Стратегия минимального вмешательства:
const isOpen = awesomplete.ul && awesomplete.ul.childNodes.length;
const selectedIndex = awesomplete.index;
awesomplete.list = newList;
if (isOpen) {
awesomplete.evaluate();
awesomplete.index = selectedIndex;
}
evaluate() пересчитывает список без полного пересоздания
компонента. Это позволяет сохранить контекст взаимодействия
пользователя.
Вместо постоянной замены списка можно использовать встроенную фильтрацию:
const list = ["apple", "apricot", "banana", "blueberry"];
const awesomplete = new Awesomplete(input, {
list,
filter: Awesomplete.FILTER_CONTAINS
});
В этом случае стратегия обновления сводится к изменению входного значения, а не массива данных. Это особенно эффективно при статических наборах данных.
При работе с API часто используется кэширование запросов, чтобы избежать повторных обращений:
const cache = new Map();
async function getSuggestions(query) {
if (cache.has(query)) return cache.get(query);
const res = await fetch(`/api/suggest?q=${query}`);
const data = await res.json();
cache.set(query, data.results);
return data.results;
}
Интеграция с Awesomplete:
input.addEventListener("input", async () => {
const results = await getSuggestions(input.value);
awesomplete.list = results;
});
Кэш особенно эффективен при повторяющихся запросах и ограниченных серверных ресурсах.
Часто обновление списка сопровождается необходимостью динамической сортировки:
function sortSuggestions(items, query) {
return items.sort((a, b) => {
return a.indexOf(query) - b.indexOf(query);
});
}
awesomplete.list = sortSuggestions(data.results, input.value);
Сортировка может быть адаптирована под разные стратегии: по релевантности, алфавиту или частоте использования.
Некоторые интерфейсы обновляют список только при повторном фокусе на поле ввода:
input.addEventListener("blur", () => {
awesomplete.list = [];
});
input.addEventListener("focus", async () => {
awesomplete.list = await getInitialSuggestions();
});
Это снижает нагрузку на систему и упрощает управление состоянием в сложных формах.
Наиболее устойчивые реализации комбинируют локальный и серверный источник:
input.addEventListener("input", async () => {
const local = filterLocal(input.value);
awesomplete.list = local;
if (input.value.length > 2) {
const remote = await getRemote(input.value);
awesomplete.list = merge(local, remote);
}
});
Такой подход обеспечивает мгновенный отклик UI за счёт локальных данных и точность за счёт серверных результатов.
При сложных интерфейсах важно ограничивать частоту обновления самого компонента:
let lastUpdate = 0;
function shouldUpdate() {
const now = Date.now();
if (now - lastUpdate < 100) return false;
lastUpdate = now;
return true;
}
input.addEventListener("input", () => {
if (!shouldUpdate()) return;
awesomplete.list = generateList(input.value);
});
Этот подход защищает от чрезмерных перерисовок и снижает нагрузку на DOM.
При обновлении списка важно явно обрабатывать отсутствие данных:
awesomplete.list = data.length
? data
: ["Нет совпадений"];
Либо отключать выпадающий список:
if (!data.length) {
awesomplete.close();
}
Это улучшает предсказуемость интерфейса и снижает когнитивную нагрузку.
В зрелых системах обновление списка в Awesomplete представляет собой комбинацию нескольких уровней:
Такая модель позволяет использовать Awesomplete не как простой автокомплит, а как компонент управляемого поиска с предсказуемым состоянием и масштабируемым поведением.