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

Архитектура компонентного дерева без каскадных ререндеров

Как избежать каскадных ререндеров в React: практические приемы мемоизации, локального состояния и селекторов контекста.

reactperformancearchitecturestate-management

Если ваш список дергается, а панель фильтров перерисовывается при изменении любого поля, скорее всего, вы столкнулись с каскадными ререндеры. Профилирование покажет, где происходит лишняя работа, а правильная архитектура как ее избавиться.

Что вызывают каскадные ререндеры?

Каскадные ререндеры это ситуация, когда изменение состояния в одном узле заставляет React пере-рендерить всю ветку, иногда и все приложение. Чаще всего виноваты “широкие” пропсы, неэффективные контексты и отсутствие мемоизации.

ПризнакЧто происходитКак исправить
Пропсы передаются “сквозными” уровнямиКаждый уровень получает новый объект и перерисовываетсяВыделить состояние в более низкие компоненты или использовать React.memo
Контекст меняется частоВсе подписчики получают новые значенияДелить контекст на части, использовать селекторы (см. ниже)
Функции создаются в рендереПри каждом рендере новые ссылки → дочерние memo-компоненты считают пропсы измененнымиОбернуть функцию в useCallback

Разделяем состояние: локальное vs глобальное

Самый простой способ сократить “тучу” перерисовок хранить состояние как можно ближе к тем компонентам, которые его используют. Если глобальный store (Redux, Zustand) содержит большой объект, каждый раз при обновлении части этого объекта все подписчики получают новые пропсы.

// Плохой вариант: один большой store
import create from "zustand";

const useStore = create((set) => ({
  filter: "",
  items: [],
  sort: "asc",
  // …
}));

function App() {
  const { filter, items, sort } = useStore(); // каждый ререндер новые ссылки
  return <>...</>;
}

Практика

  1. Локальное состояние храните filter в компоненте фильтра, а items в списке.
  2. Селекторы в Zustand: useStore(state => state.filter); в Redux: useSelector(state => state.filter).
  3. Контекст только для “темных” данных например, тема оформления, а не список продуктов.

Подробнее о мемоизации компонентов можно почитать в мемоизация компонентов.

Мемоизация компонентов и функций

React.memo ваш первый щит против каскадных ререндеров. Он сравнивает текущие и предыдущие пропсы “поверхностно”. Если пропсы объекты, стоит добавить useMemo/useCallback.

import React, { useMemo } from "react";

const Item = React.memo(({ data }: { data: { id: string; title: string } }) => {
  console.log("render Item", data.id);
  return <li>{data.title}</li>;
});

function List({ items }: { items: Array<{ id: string; title: string }> }) {
  // memoize массив → ссылка меняется только при реальных изменениях
  const memoizedItems = useMemo(() => items, [items]);
  return (
    <ul>
      {memoizedItems.map((item) => (
        <Item key={item.id} data={item} />
      ))}
    </ul>
  );
}

Обратите внимание, что useMemo здесь не “кеширует” массив, а гарантирует стабильную ссылку, что спасает от лишних рендеров Item.

Если список большой, рассмотрите виртуализацию списка она избавит от рендера элементов, не попавших в viewport.

Контекст и селекторы: минимизируем подпитку

Контекст удобен, но частое изменение заставляет всех подписчиков перерисовываться. Разделите контекст и подпишитесь только на нужные свойства через селектор.

import React, { useState, useMemo } from "react";
import { useContextSelector } from "use-context-selector";

// ThemeContext хранит только тему
const ThemeContext = React.createContext<{
  mode: string;
  setMode: React.Dispatch<React.SetStateAction<string>>;
}>({
  mode: "light",
  setMode: () => {},
});

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [mode, setMode] = useState("light");
  const value = useMemo(() => ({ mode, setMode }), [mode]);
  return (
    <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
  );
}

function ThemedButton() {
  // селектор "mode" обновляемся только при смене темы
  const mode = useContextSelector(ThemeContext, (ctx) => ctx.mode);
  return <button className={mode}>Click</button>;
}

Библиотека use-context-selector (или аналог в React 18) позволяет подписываться только на нужные свойства, тем самым устраняя каскадные ререндеры от глобального контекста.

Чеклист: чистое дерево компонентов

  1. Компоненты-мемы оберните каждый “тяжелый” компонент в React.memo.
  2. Функции в пропсах оборачивайте в useCallback.
  3. Объекты/массивы в пропсах кешируйте через useMemo.
  4. Контекст держите в нем только статичные данные или используйте селекторы.
  5. Профайлинг откройте React DevTools → Profiler, найдите горячие зоны и примените приемы выше.

Каскадные ререндеры почти всегда следствие избыточного положения состояния и отсутствия мемоизации.

Правильная архитектура компонентного дерева делает UI плавным, а каждое обновление происходит только тогда, когда действительно нужно. Это особенно ценно в больших приложениях с интерактивными списками и сложными формами.

Для более глубокого понимания процессов рендеринга изучите рендеринг и реконсиляцию в React.

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

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

Написать в Telegram

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