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

Можно ли закэшировать функцию с помощью useMemo?

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

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

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

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

# Кэширование функции с помощью useMemo

Это очень важный вопрос, который проверяет глубокое понимание React хуков. Краткий ответ: технически можно, но это НЕ правильный подход. Давайте разберёмся подробно.

Краткий ответ

Да, технически useMemo может вернуть функцию и кэшировать её. Но это неправильно. Для кэширования функций используй useCallback.

Почему можно использовать useMemo

усeMemo кэширует любое значение, включая функции:

function Example() {
  // useMemo может вернуть функцию
  const memoizedFunction = useMemo(() => {
    return function handleClick() {
      console.log('clicked');
    };
  }, []);
  
  return <button onClick={memoizedFunction}>Click</button>;
}

Технически это работает, но это плохая практика.

Почему это НЕ правильно

1. Нечитаемость и путанность

useMemo предназначен для дорогих вычислений, а не функций:

// ❌ Плохо - путающий код
const memoizedFn = useMemo(() => {
  return () => console.log('clicked');
}, []);

// ✅ Хорошо - ясный код
const memoizedFn = useCallback(() => {
  console.log('clicked');
}, []);

2. useCallback - это специально для функций

useCallback — это специальный хук, созданный именно для кэширования функций:

// useCallback это буквально:
// const useCallback = (fn, deps) => useMemo(() => fn, deps);

// Вот реальная реализация useCallback:
function useCallback(callback, dependencies) {
  return useMemo(() => callback, dependencies);
}

3. Семантика кода

Люди, читающие ваш код, ожидают useMemo для вычислений, а useCallback для функций:

// Когда видит useMemo, думает: "Здесь дорогие вычисления"
const result = useMemo(() => computeHeavyValue(), [deps]);

// Когда видит useCallback, думает: "Здесь функция для передачи в дочерний компонент"
const handleClick = useCallback(() => { }, [deps]);

Правильный способ - useCallback

function Component({ onUpdate }) {
  // ✅ Правильно
  const handleClick = useCallback(() => {
    onUpdate({ id: 1, name: 'John' });
  }, [onUpdate]);
  
  return <Button onClick={handleClick}>Update</Button>;
}

Практические примеры

Пример 1: Передача функции в дочерний компонент

function Parent() {
  const [count, setCount] = React.useState(0);
  
  // ❌ ПЛОХО: функция пересоздаётся на каждый рендер
  function handleClick() {
    setCount(count + 1);
  }
  
  return (
    <div>
      <Child onButtonClick={handleClick} />
    </div>
  );
}

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

// Проблема: Child будет перерендеряться на каждый рендер Parent
// потому что handleClick это новая функция каждый раз
function ParentFixed() {
  const [count, setCount] = React.useState(0);
  
  // ✅ ХОРОШО: useCallback кэширует функцию
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  return (
    <div>
      <Child onButtonClick={handleClick} />
    </div>
  );
}

const Child = React.memo(({ onButtonClick }) => {
  console.log('Child rendered'); // Теперь не перерендеряется
  return <button onClick={onButtonClick}>Click</button>;
});

Пример 2: Функция как зависимость других эффектов

function DataFetcher({ searchTerm, onDataFetch }) {
  // ❌ ПЛОХО: useEffect будет срабатывать часто
  React.useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`/api/search?q=${searchTerm}`);
      const data = await response.json();
      onDataFetch(data);
    };
    fetchData();
  }, [searchTerm, onDataFetch]); // onDataFetch пересоздаётся часто
}

function ParentBad() {
  const [results, setResults] = React.useState([]);
  
  // Эта функция пересоздаётся на каждый рендер
  const handleDataFetch = (data) => {
    setResults(data);
  };
  
  return <DataFetcher searchTerm="react" onDataFetch={handleDataFetch} />;
}

// ✅ ХОРОШО: useCallback стабилизирует функцию
function ParentFixed() {
  const [results, setResults] = React.useState([]);
  
  const handleDataFetch = useCallback((data) => {
    setResults(data);
  }, []);
  
  return <DataFetcher searchTerm="react" onDataFetch={handleDataFetch} />;
}

Пример 3: Использование функции в зависимостях

function Component() {
  const [data, setData] = React.useState(null);
  
  // ❌ ПЛОХО: filter функция создаётся каждый рендер
  // useEffect будет срабатывать постоянно
  React.useEffect(() => {
    const filter = (items) => items.filter(item => item.active);
    const filtered = filter(data);
    console.log(filtered);
  }, [data, filter]); // filter пересоздаётся каждый рендер
}

function ComponentFixed() {
  const [data, setData] = React.useState(null);
  
  // ✅ ХОРОШО: filter кэшируется
  const filter = useCallback(
    (items) => items.filter(item => item.active),
    []
  );
  
  React.useEffect(() => {
    const filtered = filter(data);
    console.log(filtered);
  }, [data, filter]); // filter стабилен
}

Когда useCallback критичен

1. Передача в React.memo компонент

const ExpensiveChild = React.memo(({ onCallback }) => {
  return <div>Child</div>;
});

function Parent() {
  // Без useCallback, ExpensiveChild перерендеряется
  const onCallback = useCallback(() => {}, []);
  return <ExpensiveChild onCallback={onCallback} />;
}

2. Зависимость в useEffect

function Component() {
  const handleChange = useCallback(() => {
    // ...
  }, []);
  
  useEffect(() => {
    // subscribe использует handleChange
    subscribe(handleChange);
  }, [handleChange]); // handleChange стабилен
}

3. Передача в событий для библиотек

function Component() {
  const handleChange = useCallback((value) => {
    // ...
  }, []);
  
  React.useEffect(() => {
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [handleChange]);
}

Сравнение useMemo и useCallback

// useMemo кэширует РЕЗУЛЬТАТ вычисления
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

// useCallback кэширует ФУНКЦИЮ
const callback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

// Эквивалентно:
const callback = useMemo(() => {
  return () => {
    doSomething(a, b);
  };
}, [a, b]);

// Но useCallback более читаемо и явно для функций

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

// useMemo на функции имеет небольшой оверхед
// useCallback специально оптимизирован для функций

// Это микрооптимизация, но useCallback немного быстрее
const memoized = useMemo(() => fn, deps);      // Немного медленнее
const callback = useCallback(fn, deps);        // Немного быстрее

Вывод

  1. Технически можно использовать useMemo для кэширования функций
  2. На практике всегда используй useCallback для функций
  3. useCallback — это правильный инструмент, созданный специально для этого
  4. Читаемость кода важнее, чем микрооптимизация
  5. Семантика кода помогает другим разработчикам понять ваши намерения

Используй правильный инструмент для правильной задачи!

Можно ли закэшировать функцию с помощью useMemo? | PrepBro