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

В каких случаях нужна мемоизация callback

2.3 Middle🔥 171 комментариев
#React#Оптимизация и производительность

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

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

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

Мемоизация callback функций в React

useCallback — это хук для мемоизации функций-обработчиков. Он сохраняет функцию в памяти между рендерами, если зависимости не изменились. Разберем, когда это действительно нужно.

Основная проблема без мемоизации

В React каждый рендер компонента создает новые функции:

function Parent() {
  // Эта функция создается заново при каждом рендере
  const handleClick = () => console.log('Clicked!');
  
  return <Child onClick={handleClick} />;
}

function Child({ onClick }) {
  // Каждый раз onClick имеет новую ссылку, 
  // Child будет перерендериться даже если логика не изменилась
  return <button onClick={onClick}>Click me</button>;
}

Когда нужен useCallback

1. Передача callback в зависимостях эффектов

Это самый важный случай. Когда callback включен в зависимости useEffect, он должен быть стабильным:

function SearchComponent() {
  const [query, setQuery] = useState('');
  
  // БЕЗ useCallback — эффект запускается на каждый рендер
  const fetchResults = () => {
    fetch(`/api/search?q=${query}`);
  };
  
  useEffect(() => {
    fetchResults(); // fetchResults меняется каждый рендер!
  }, [fetchResults]); // Бесконечный цикл
  
  return <input onChange={(e) => setQuery(e.target.value)} />;
}

// ПРАВИЛЬНО:
function SearchComponent() {
  const [query, setQuery] = useState('');
  
  const fetchResults = useCallback(() => {
    fetch(`/api/search?q=${query}`);
  }, [query]); // Функция меняется только если query меняется
  
  useEffect(() => {
    fetchResults();
  }, [fetchResults]); // Теперь это безопасно
  
  return <input onChange={(e) => setQuery(e.target.value)} />;
}

2. Оптимизация перерендеров дочерних компонентов

Когда дочерний компонент оптимизирован через React.memo(), стабильная функция предотвращает ненужные перерендеры:

const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});

function Parent() {
  // БЕЗ useCallback — Child перерендеривается при каждом рендере Parent
  const handleClick = () => console.log('Clicked');
  
  return <Child onClick={handleClick} />;
}

// ПРАВИЛЬНО:
function Parent() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // Функция не меняется
  
  return <Child onClick={handleClick} />; // Child не перерендеривается
}

3. Функции как зависимости других хуков

Если callback нужен в useMemo или других хуках:

function FormComponent() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  
  // Стабильный колбэк для валидации
  const validateForm = useCallback(() => {
    return formData.name.length > 0 && formData.email.includes('@');
  }, [formData]);
  
  // Используем его в другом хуке
  const isValid = useMemo(() => {
    return validateForm();
  }, [validateForm]);
  
  return <div>Valid: {isValid ? 'Yes' : 'No'}</div>;
}

Когда мемоизация НЕ нужна

1. Простая передача props без оптимизации

Если дочерний компонент не мемоизирован, useCallback просто тратит ресурсы:

// Ненужно:
function Parent() {
  const handleClick = useCallback(() => {
    // ...
  }, []);
  
  return <div onClick={handleClick}>Click</div>; // div не мемоизирован
}

2. Callback не в зависимостях эффектов

Если функция не используется в useEffect или других хуках, мемоизация не помогает:

// Можно без useCallback:
function Button() {
  const handleClick = () => console.log('Clicked');
  
  return <button onClick={handleClick}>Click</button>;
}

Правило большого пальца

Используй useCallback если:

  • Callback передается в зависимости useEffect или другого хука
  • Дочерний компонент оптимизирован React.memo()
  • Callback используется в useMemo()

Не использовать, если callback просто передается как prop и больше нигде не применяется.

Важный нюанс с зависимостями

Часто разработчики создают циклическую зависимость:

// ПЛОХО — порочный круг:
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = useCallback(() => {
    setCount(count + 1); // count в зависимостях!
  }, [count]); // increment меняется при каждом count
  
  useEffect(() => {
    const timer = setInterval(increment, 1000);
    return () => clearInterval(timer);
  }, [increment]); // Эффект перезагружается при каждом count!
}

// ПРАВИЛЬНО — используй функциональное обновление:
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = useCallback(() => {
    setCount(prev => prev + 1); // Функциональное обновление
  }, []); // Зависимостей нет!
  
  useEffect(() => {
    const timer = setInterval(increment, 1000);
    return () => clearInterval(timer);
  }, [increment]);
}

Производительность

useCallback сам по себе имеет стоимость — он выполняет сравнение зависимостей. Если зависимостей больше, чем экономии от мемоизации, это не оправдано. Профилируй React DevTools Profiler перед оптимизацией.

В каких случаях нужна мемоизация callback | PrepBro