Назад
Назад к заметкам
· 5 мин чтения

Рендеринг и реконсиляция в React

Как управлять рендерингом и реконсиляцией в React: виртуальный DOM, memo, useCallback, виртуализация и профилирование для больших списков.

reactperformanceoptimizationrenderingreconciliation

Введение

Если ваш список в UI тормозит, а консоль полна предупреждений о лишних рендерах, значит, вы столкнулись с проблемой Рендеринг и реконсиляция в React. На первый взгляд все выглядит просто: передаете новые props React пересчитывает дерево. На деле каждый лишний проход потерянные миллисекунды и ухудшенный UX.

Разберем реальный кейс: массив из 10 000 записей, который пользователь может фильтровать и сортировать. Показаны инструменты, контролирующие процесс, и те, что лишь создают иллюзию оптимизации.

Фокус конкретные шаги, которые можно внедрить за полчаса и увидеть разницу.

  1. Как работает виртуальный DOM и почему его “diff” иногда падает в тупик.
  2. Правильное использование React.memo и useCallback.
  3. Практика: разбиваем большой список на “ленивые” части с react-window.
  4. Сравнение локального и глобального хранилищ (Redux, Zustand).
  5. Измерение реального времени рендера через Profiler.
  6. Чеклист типичных подводных камней.

1. Как работает виртуальный DOM

Виртуальный DOM легкая копия реального дерева узлов. При изменении состояния React создает новое дерево, сравнивает его с предыдущим и генерирует минимальный набор операций.

Почему сравнение может быть дорогим?

  • Каждый элемент сравнивается по типу и ключу.
  • Если у списка нет стабильных key, React считает каждый элемент новым.
  • Глубокие вложения увеличивают количество проверок.
const items = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `Item ${i}`,
}));

function List() {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Таблица сравнения: ключи vs. отсутствие ключей

СитуацияОперации DOMВремя рендера (мс)
С key (stable)10 000 → 0~12
Без key10 000 → 10 000~45

Вывод: стабильно уникальные key ваш первый щит от лишних обновлений. Это центральный элемент в теме Рендеринг и реконсиляция в React.

2. Мемоизация компонентов

React.memo хранит предыдущие пропсы и пропускает рендер, если они не изменились. В комбинации с useCallback можно “заморозить” функции, которые передаются вниз по дереву. Детальный разбор этих инструментов смотрите в статье React.memo vs useMemo.

const Item = React.memo(({ name, onClick }) => {
  console.log("render", name);
  return <li onClick={onClick}>{name}</li>;
});

export default function List({ data }) {
  const handleClick = React.useCallback((id) => {
    console.log("clicked", id);
  }, []);

  return (
    <ul>
      {data.map((item) => (
        <Item
          key={item.id}
          name={item.name}
          onClick={() => handleClick(item.id)}
        />
      ))}
    </ul>
  );
}

Типичные ловушки:

  • Не оборачивать стрелочную функцию в useCallback каждый рендер создает новую функцию, React.memo не работает.
  • Переиспользовать объект-пропс без useMemo объект считается новым каждый раз.

Эти приемы напрямую влияют на Рендеринг и реконсиляцию в React, уменьшая количество лишних проходов.

3. Ленивый рендер списков

Когда список содержит десятки тысяч элементов, даже с key и memo рендер остается тяжелым. Здесь помогает виртуализация списков отрисовываем лишь видимую часть.

import { FixedSizeList as List } from "react-window";

const Row = ({ index, style }) => <div style={style}>Row {index}</div>;

export default function VirtualizedList() {
  return (
    <List height={400} itemCount={10000} itemSize={35} width={300}>
      {Row}
    </List>
  );
}

Плюсы виртуализации:

  • Память: одновременно в DOM только 12-15 узлов.
  • Перерасчет происходит только при скролле.

Чек-лист:

  1. itemCount > 5000 включайте react-window.
  2. Высота строки должна быть фиксирована; иначе используйте VariableSizeList.
  3. Не помещайте position: absolute-элементы внутри строки они ломают расчет.

4. Где хранить состояние

Частая ошибка держать весь массив в глобальном хранилище (Redux) и каждый раз передавать его в компоненты. Это заставляет каждый подписчик получать новые ссылки, даже если данные не изменились.

Плохой подход:

// store.js
export const itemsSlice = createSlice({
  name: "items",
  initialState: [],
  reducers: { setItems: (state, action) => action.payload },
});
// Component.jsx
const items = useSelector((state) => state.items);
return <LargeList data={items} />; // каждый dispatch = новый массив

Лучшее решение локальное состояние + селекторы:

function FilterableList() {
  const [filter, setFilter] = React.useState("");
  const data = React.useMemo(() => {
    return allItems.filter((i) => i.name.includes(filter));
  }, [filter]);

  return <VirtualizedList data={data} />;
}

Преимущества локального хранилища:

  • Перерисовка только при изменении фильтра.
  • Нет лишних подписок на глобальный store.
  • Память используется экономнее.

Эти рекомендации уменьшают нагрузку на Рендеринг и реконсиляцию в React.

5. Профилирование рендера

React 18 имеет встроенный <Profiler> оберните интересующий участок и получите детали о времени “commit” и “render”.

import { Profiler } from "react";

function onRender(id, phase, actualDuration, baseDuration) {
  console.log({ id, phase, actualDuration, baseDuration });
}

export default function App() {
  return (
    <Profiler id="LargeList" onRender={onRender}>
      <VirtualizedList />
    </Profiler>
  );
}

Ключевые метрики:

  • actualDuration реальное время рендера.
  • baseDuration оценка без оптимизаций.

Сравнение показывает, насколько эффективно работают memo- и виртуализационные приемы, т.е. как они влияют на Рендеринг и реконсиляцию в React.

6. Практический рецепт “минимального рендера”

Соберите все в один шаблон, который можно скопировать в любой проект.

  1. Установите уникальный key.
  2. Оберните элементы в React.memo.
  3. Передавайте функции через useCallback.
  4. Если список > 5000 элементов подключите react-window.
  5. Храните фильтры локально, а не в глобальном store.
  6. Проверьте время через <Profiler> и фиксируйте регрессии.

Таблица внедрения:

ШагЧто делаемПочемуБиблиотека
1keyПредотвратить полное пересоздание элементов
2React.memoСократить повторные рендеры
3useCallbackСтабильные функции-пропсы
4ВиртуализацияОбрезать DOM-деревоreact-window
5Локальный useMemoМинимизировать пересчеты
6<Profiler>Видеть реальную нагрузкуReact

FAQ / чеклист

  • Не забывайте key. Даже с memo без ключа список будет полностью пересоздаваться.
  • Оборачивайте в useCallback только те функции, которые действительно передаются вниз. Иначе добавляется лишний слой.
  • Виртуализируйте только при необходимости. Излишняя виртуализация усложняет код и может ухудшить SEO.
  • Не храните отфильтрованный массив в Redux. Каждый dispatch создает новую ссылку и приводит к массовому ререндеру.
  • Проверяйте <Profiler> после изменений. Без измерений невозможно понять, помогли ли оптимизации.

Любой проект, где UI оперирует большими массивами (таблицы, ленты, чат-истории), выигрывает от описанных техник. После их применения заметно снижается количество “Too many re-renders” и улучшается отклик интерфейса.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов