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

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

2.0 Middle🔥 241 комментариев
#React

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

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

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

Основные хуки для оптимизации и предотвращения ререндеров в React

В современном React функциональные компоненты предоставляют несколько встроенных хуков и паттернов, позволяющих контролировать и предотвращать излишние ререндеры. Вот ключевые инструменты, отсортированные по частоте и эффективности использования:

1. useMemo - мемоизация вычислений

Запоминает результат вычислений между рендерами, предотвращая повторные вычисления при неизменных зависимостях.

import React, { useMemo } from 'react';

const ExpensiveComponent = ({ items, filter }) => {
  // Вычисление выполняется только при изменении items или filter
  const filteredItems = useMemo(() => {
    console.log('Выполняются тяжелые вычисления...');
    return items.filter(item => item.includes(filter));
  }, [items, filter]); // Массив зависимостей

  return <div>{filteredItems.map(item => <span key={item}>{item}</span>)}</div>;
};

2. useCallback - мемоизация функций

Сохраняет ссылку на функцию между рендерами, что критично для оптимизации дочерних компонентов.

import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  
  // Функция не пересоздается при каждом рендере
  const handleClick = useCallback(() => {
    console.log('Обработчик клика', count);
  }, [count]); // Зависимость от count

  return (
    <>
      <button onClick={() => setCount(count + 1)}>Увеличить: {count}</button>
      {/* ChildComponent не будет перерендериваться без необходимости */}
      <ChildComponent onClick={handleClick} />
    </>
  );
};

3. useRef - сохранение изменяемых значений

Создает мутабельный объект, который сохраняется между рендерами без их инициирования.

import React, { useRef, useEffect, useState } from 'react';

const RenderCounter = () => {
  const [value, setValue] = useState('');
  const renderCount = useRef(0); // Не вызывает ререндер при изменении
  const inputRef = useRef(null);

  useEffect(() => {
    renderCount.current = renderCount.current + 1;
  });

  const focusInput = () => {
    inputRef.current.focus(); // Работа с DOM без ререндера
  };

  return (
    <div>
      <input 
        ref={inputRef}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <p>Компонент отрендерен {renderCount.current} раз</p>
      <button onClick={focusInput}>Фокус на инпуте</button>
    </div>
  );
};

4. React.memo - мемоизация компонентов (HOC, а не хук)

Хотя это не хук, а компонент высшего порядка, он тесно связан с оптимизацией рендеров и часто используется с useCallback.

import React, { memo } from 'react';

const ExpensiveChildComponent = memo(({ data, onClick }) => {
  console.log('Дочерний компонент рендерится');
  return (
    <div onClick={onClick}>
      {data.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
}, (prevProps, nextProps) => {
  // Кастомная функция сравнения (опционально)
  return prevProps.data.length === nextProps.data.length;
});

Комбинированные стратегии оптимизации

Комбинация useCallback + React.memo

const Parent = () => {
  const [state, setState] = useState(0);
  
  // Без useCallback дочерний компонент будет ререндериться всегда
  const stableHandler = useCallback(() => {
    console.log('Стабильная функция');
  }, []); // Пустой массив зависимостей - функция создается один раз

  return (
    <>
      <button onClick={() => setState(state + 1)}>Ререндер родителя</button>
      <MemoizedChild onAction={stableHandler} />
    </>
  );
};

const Child = ({ onAction }) => {
  console.log('Рендер дочернего компонента');
  return <button onClick={onAction}>Действие</button>;
};

const MemoizedChild = memo(Child); // Ререндерится только при изменении пропсов

Контекст без лишних ререндеров с useMemo

const UserContext = React.createContext();

const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  // Значение контекста мемоизируется
  const contextValue = useMemo(() => ({
    user,
    login: (userData) => setUser(userData),
    logout: () => setUser(null)
  }), [user]); // Пересоздается только при изменении user

  return (
    <UserContext.Provider value={contextValue}>
      {children}
    </UserContext.Provider>
  );
};

Практические рекомендации

Когда использовать:

  • useMemo: Для тяжелых вычислений, преобразований данных, создания объектов/массивов
  • useCallback: Для функций, передаваемых в оптимизированные дочерние компоненты
  • useRef: Для хранения мутабельных значений без триггера ререндера
  • React.memo: Для чистых компонентов с одинаковыми пропсами

Предостережения:

  1. Не оптимизируйте преждевременно - React достаточно быстр для большинства случаев
  2. Профилируйте производительность с помощью React DevTools
  3. Пустые массивы зависимостей в useMemo/useCallback могут приводить к багам
  4. Слишком агрессивная мемоизация может потреблять больше памяти, чем сэкономит

Антипаттерны:

// ❌ Плохо - useMemo для простых вычислений
const value = useMemo(() => x + y, [x, y]);

// ✅ Лучше - простое выражение
const value = x + y;

// ❌ Плохо - создание функции в useCallback без зависимостей
const handler = useCallback(() => {
  console.log(state); // Будет всегда использовать初始льное значение state
}, []);

// ✅ Правильно - указание зависимостей
const handler = useCallback(() => {
  console.log(state);
}, [state]);

Эти инструменты образуют мощную систему оптимизации рендеров в React, позволяя точно контролировать, когда и какие части приложения должны обновляться в ответ на изменения состояния.

Какими hooks можно заблокировать ререндер? | PrepBro