← Назад к вопросам

Как можно оптимизировать рендер?

1.0 Junior🔥 81 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация рендера в React

Рендер — это одна из самых дорогих операций в браузере. Если рендерится лишний раз, падает производительность. Давай разберемся в стратегиях.

Почему рендер медленный

Когда React перерисовывает компонент:

  1. Выполняются все функции компонента
  2. Пересчитываются все зависимости
  3. Обновляется DOM (если что-то изменилось)
  4. Браузер перерисовывает страницу (repaint, reflow)

Если вы неправильно структурируете код, родитель может триггерить перерендер 100+ дочерних компонентов просто так.

Способ 1: memo() — мемоизация компонента

Это оборачивает компонент и предотвращает перерендер если props не изменились:

// ДО: будет перерендериваться каждый раз
function UserCard({ user, onClick }) {
  console.log("UserCard рендерится");
  return <div onClick={onClick}>{user.name}</div>;
}

// ПОСЛЕ: будет перерендериваться только если user или onClick изменились
const UserCard = memo(function({ user, onClick }) {
  console.log("UserCard рендерится");
  return <div onClick={onClick}>{user.name}</div>;
});

Важно: memo сравнивает props поверхностно (shallow). Если вы передаете объект:

function Parent() {
  const user = { id: 1, name: "Анна" }; // Новый объект каждый раз!
  
  return <UserCard user={user} />; // memo не поможет
}

Вы получите перерендер всё равно, потому что user — новый объект.

Способ 2: useMemo() — мемоизация значения

Это кеширует дорогое вычисление и обновляет только если зависимости изменились:

function Parent() {
  // ПЛОХО: user создается каждый раз
  const user = { id: 1, name: "Анна" };
  
  // ХОРОШО: user создается только если зависимости изменились
  const user = useMemo(() => (
    { id: 1, name: "Анна" }
  ), []); // Пустой массив = создается один раз
  
  return <UserCard user={user} />;
}

Реальный пример — дорогое вычисление:

function Leaderboard({ players }) {
  // Сортировка 10000+ записей — дорого!
  const sortedPlayers = useMemo(() => {
    console.log("Сортирую...");
    return [...players].sort((a, b) => b.score - a.score);
  }, [players]); // Пересчитываем только если players изменились
  
  return (
    <ul>
      {sortedPlayers.map(p => <li key={p.id}>{p.name}: {p.score}</li>)}
    </ul>
  );
}

Способ 3: useCallback() — мемоизация функции

Оборачивает функцию так, чтобы она была одна и та же в памяти, если зависимости не изменились:

function Parent() {
  // ПЛОХО: onClick создается каждый раз
  const onClick = () => console.log("Клик");
  
  // ХОРОШО: onClick остается той же функцией если зависимости не изменились
  const onClick = useCallback(() => {
    console.log("Клик");
  }, []); // Пустой массив = функция создается один раз
  
  return <UserCard onClick={onClick} />;
}

Когда это нужно:

function Parent() {
  const [count, setCount] = useState(0);
  
  // ПРОБЛЕМА: onClick меняется каждый раз, memo(Button) перерендеривается
  const handleClick = () => setCount(count + 1);
  
  // РЕШЕНИЕ: callback запоминает count через замыкание
  const handleClick = useCallback(() => {
    setCount(c => c + 1); // Используем функцию-обновление
  }, []);
  
  return <Button onClick={handleClick} />;
}

Способ 4: Правильная структура компонентов

Перемещайте state ближе к месту использования:

// ПЛОХО: весь state в корне, все дети перерендеривются
function App() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [filters, setFilters] = useState({});
  
  return (
    <div>
      <SearchBox query={query} onChange={setQuery} />
      <FilterPanel filters={filters} onChange={setFilters} />
      <ResultsList results={results} />
    </div>
  );
}

// ХОРОШО: состояние разделено
function SearchSection() {
  const [query, setQuery] = useState("");
  return <SearchBox query={query} onChange={setQuery} />;
}

function FilterSection() {
  const [filters, setFilters] = useState({});
  return <FilterPanel filters={filters} onChange={setFilters} />;
}

function App() {
  const [results, setResults] = useState([]);
  
  return (
    <div>
      <SearchSection />
      <FilterSection />
      <ResultsList results={results} />
    </div>
  );
}

Теперь изменение query не влияет на FilterSection и ResultsList.

Способ 5: Разделение state в Context

Если вам нужен глобальный state, разделите его:

// ПЛОХО: один Context с всем
const AppContext = createContext({
  theme: "light",
  language: "ru",
  user: null,
  notifications: []
});

// ХОРОШО: отдельные Contexts
const ThemeContext = createContext("light");
const UserContext = createContext(null);
const NotificationsContext = createContext([]);

Теперь компонент, использующий только theme, не перерендеривается при изменении notifications.

Способ 6: Виртуализация длинных списков

Не рендерьте все элементы списка, только видимые:

import { FixedSizeList } from 'react-window';

function LongList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}

Вместо рендера 10000 элементов, рендерим только 30 видимых на экране.

Способ 7: Ленивая загрузка компонентов

Загружайте тяжелые компоненты только когда они нужны:

const HeavyEditor = lazy(() => import('./HeavyEditor'));

function App() {
  const [showEditor, setShowEditor] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowEditor(true)}>Открыть редактор</button>
      {showEditor && (
        <Suspense fallback={<div>Загружается...</div>}>
          <HeavyEditor />
        </Suspense>
      )}
    </div>
  );
}

Способ 8: Правильное использование ключей в списках

// ПЛОХО: индекс как ключ (при удалении элемента всё перепутается)
list.map((item, index) => <Item key={index} item={item} />)

// ХОРОШО: уникальный ID
list.map(item => <Item key={item.id} item={item} />)

Инструменты для анализа

React DevTools Profiler:

  • Откройте DevTools -> Profiler
  • Запишите сессию
  • Посмотрите, какие компоненты рендерились и почему

console.log с условиями:

function UserCard({ user }) {
  if (process.env.NODE_ENV === 'development') {
    console.log("UserCard рендерится", user.id);
  }
  return <div>{user.name}</div>;
}

Чеклист оптимизации

  1. Используй memo() для компонентов с дорогим рендером
  2. Используй useMemo() для дорогих вычислений
  3. Используй useCallback() для колбэков, переданных в memo() компоненты
  4. Раздели state по блокам (не держи всё в корне)
  5. Раздели Context если у тебя много значений
  6. Используй react-window для длинных списков
  7. Ленивая загрузка тяжелых компонентов
  8. Правильные ключи в списках

Помни: оптимизация нужна только если есть проблема. Профилируй сначала, оптимизируй потом.