Приведи пример сложного решения за последние полгода-год
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример сложного решения: Оптимизация рендеринга крупной таблицы с виртуализацией и Web Workers
За последний год наиболее сложной задачей стала оптимизация производительности для финансового веб-приложения, работающего с огромными массивами данных. Мы столкнулись с проблемой рендеринга таблицы, содержащей до 100 000 строк и 50 столбцов. Нагрузка на DOM и блокировка основного потока делали интерфейс полностью неотзывчивым. Решение потребовало комплексного подхода, сочетающего виртуализацию, Web Workers и ленивую загрузку.
Проблемы и анализ
Исходный компонент таблицы рендерил все строки одновременно, что приводило к:
- Задержкам ввода (>5 секунд при скролле).
- Потреблению памяти >500 МБ в браузере.
- Блокировке основного потока из-за тяжелых вычислений (фильтрация, сортировка).
Реализованное решение
1. Виртуализация с использованием библиотеки TanStack Table
Мы выбрали TanStack Table (ранее React Table) за его гибкость и низкий уровень абстракции. Реализовали окно виртуализации, отображающее только видимые строки (~30-50 вместо 100 000).
import { useVirtualizer } from '@tanstack/react-virtual';
const VirtualizedTable = ({ data, columns }) => {
const tableContainerRef = useRef(null);
const virtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 40, // Высота строки
overscan: 10, // Буферные строки для плавного скролла
});
return (
<div ref={tableContainerRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<TableRow
key={virtualRow.key}
data={data[virtualRow.index]}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
/>
))}
</div>
</div>
);
};
2. Вынос вычислений в Web Worker
Фильтрация и сортировка выполнялись в отдельном потоке через Web Worker, чтобы не блокировать UI.
// worker.js
self.onmessage = (event) => {
const { data, filters, sortBy } = event.data;
// Тяжелая логика фильтрации/сортировки
const filtered = data.filter(item =>
filters.every(f => item[f.field].includes(f.value))
);
const sorted = filtered.sort((a, b) =>
a[sortBy.field] > b[sortBy.field] ? 1 : -1
);
self.postMessage(sorted);
};
// main.js
const worker = new Worker('./worker.js');
worker.postMessage({ data: hugeDataset, filters, sortBy });
worker.onmessage = (event) => {
setVisibleData(event.data.slice(0, 100)); // Ленивая загрузка
};
3. Ленивая загрузка данных
Мы разделили данные на чанки по 1000 записей и загружали их по мере скролла, интегрируя с Intersection Observer.
const LazyLoader = () => {
const [loadedChunks, setLoadedChunks] = useState(0);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && loadedChunks * 1000 < totalData) {
fetchChunk(loadedChunks).then(newData => {
setData(prev => [...prev, ...newData]);
setLoadedChunks(prev => prev + 1);
});
}
});
observer.observe(loadTriggerRef.current);
return () => observer.disconnect();
}, [loadedChunks]);
return <div ref={loadTriggerRef} />;
};
4. Оптимизация перерисовок с React.memo и useMemo
Каждая строка таблицы была обернута в React.memo, а вычисляемые значения кэшированы через useMemo.
const TableRow = React.memo(({ data }) => {
const formattedValues = useMemo(() =>
formatData(data), [data]
);
return <tr>{formattedValues}</tr>;
});
Результаты
После внедрения решения:
- Время первого рендера сократилось с 12 до 0.3 секунд.
- Потребление памяти упало на 80% (до ~100 МБ).
- Плавность скролла достигла 60 FPS даже на слабых устройствах.
- Основной поток больше не блокировался вычислениями.
Выводы
Этот опыт показал, что работа с большими данными на фронтенде требует глубокого понимания:
- Механизмов рендеринга браузера и работы Event Loop.
- Архитектурных паттернов для разделения потоков.
- Инструментов виртуализации и их интеграции с фреймворками.
Решение потребовало не только технических навыков, но и тщательного профилирования производительности (Chrome DevTools, React Profiler) и а/б тестирования для проверки влияния на UX. В итоге мы создали масштабируемую систему, которую адаптировали и для других компонентов приложения.