Как оптимизировать работу с DOM без React?
Рендеринг и производительность
Без React оптимизация DOM — ручная работа: нужно самому минимизировать количество операций и не провоцировать лишние reflow/repaint.
Минимизировать доступ к DOM
- Кешировать найденные узлы в переменные вместо повторных
querySelectorв цикле. - Работать с данными, а не с DOM: собрать результат, потом один раз применить.
Батчить вставки
const fragment = document.createDocumentFragment();
items.forEach((item) => fragment.append(createRow(item)));
list.append(fragment); // одна вставка вместо NАльтернативы: собрать строку и один innerHTML (осторожно с XSS), replaceChildren().
Избегать layout thrashing
Чередование чтения (offsetHeight, getBoundingClientRect) и записи стилей в одном цикле заставляет браузер пересчитывать layout снова и снова. Разделять фазы: сначала все чтения, потом все записи. Инструмент — requestAnimationFrame для записи, FastDom как библиотека.
Дешёвые изменения
- Анимировать
transformиopacity(композитинг, без reflow), а не top/left/width. will-change/containдля изоляции; классы вместо множества inline-стилей.- Скрывать поддерево (
display: none) перед серией изменений и показывать обратно.
Меньше узлов и обработчиков
- Делегирование событий: один слушатель на контейнер вместо тысячи на элементах (именно так делает React).
- Виртуализация длинных списков: рендерить только видимое.
Дросселирование и наблюдатели
Обработчики scroll/resize — throttle/rAF; вместо onScroll+getBoundingClientRect — IntersectionObserver; ResizeObserver вместо polling.
Коротко для собеседования
Кешировать узлы, батчить вставки через DocumentFragment/replaceChildren, разделять чтение и запись layout (не провоцировать thrashing), анимировать transform/opacity, делегировать события, виртуализировать списки, throttle через rAF и использовать IntersectionObserver/ResizeObserver. По сути это ручное воспроизведение того, что React делает автоматически.