Как оптимизировать фильтрацию данных во frontend, чтобы избежать повторных проходов?
Что проверяют
Умеете ли вы найти, где на самом деле уходит время: в самой фильтрации, в пересчёте на каждый рендер или в отрисовке результата. Следом обычно спрашивают, что делать, если элементов сто тысяч и поиск тормозит при вводе.
Как отвечать
Сначала измерить: вкладка Performance в DevTools и React Profiler. Фильтр по десяти тысячам объектов занимает единицы миллисекунд, тормозит обычно рендер DOM или лишние пересчёты.
Потом приёмы по убыванию пользы:
- один проход: вместо цепочки
.filter().filter().map()собрать активные фильтры в массив предикатов и проверять элемент черезpredicates.every(p => p(item)).everyостанавливается на первомfalse, поэтому самые отсекающие условия ставьте первыми. В ES2025 появились ленивые iterator helpers (arr.values().filter().map()), они не создают промежуточных массивов; - предвычисление: строки для поиска приводите к нижнему регистру один раз при загрузке, а не в каждом сравнении. Для частых фильтров постройте индекс
Mapпо категории, тогда выборка по ней обходится без прохода по всему массиву; Setвместоincludes: мультиселект черезarray.includesпревращает фильтр в O(n·m),Set.hasвозвращает к O(n);- мемоизация:
useMemo(() => applyFilters(items, filters), [items, filters]), чтобы не пересчитывать при каждом рендере. Результат не кладите вuseStateс синхронизацией черезuseEffect: это лишний рендер и риск рассинхрона; - отзывчивость ввода:
useDeferredValueилиstartTransitionи debounce для поля поиска; - объём: виртуализация списка через TanStack Virtual или react-window, для очень больших данных фильтрация в Web Worker или на сервере с пагинацией.
Где спотыкаются
Мемоизируют по объекту filters, который создаётся заново на каждом рендере. Зависимость меняется всегда, и useMemo ничего не экономит.
Предлагают Lodash как способ ускорить фильтрацию. _.filter не быстрее нативного filter, проходов столько же.
Вызывают sort() на исходном массиве внутри мемоизированной функции. Он сортирует на месте, мутирует пропсы и ломает сравнение по ссылке. Нужен toSorted() или копия.