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

В чем разница между useMemo и useEffect?

1.8 Middle🔥 291 комментариев
#React

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

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

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

В чем разница между useMemo и useEffect?

useMemo и useEffect - два разных хука React, которые часто путают начинающие разработчики. Они решают разные проблемы и работают в разных фазах жизненного цикла компонента.

Основное различие

useMemo - оптимизирует вычисления (render phase):

// useMemo вычисляет значение ДО финального рендера
const memoValue = useMemo(() => {
  return expensiveCalculation(data);
}, [data]);

// Значение вычисляется синхронно и используется в JSX
return <Component value={memoValue} />;

useEffect - выполняет побочные эффекты (commit phase):

// useEffect выполняется ПОСЛЕ рендера
useEffect(() => {
  console.log('Component rendered');
  document.title = `Count: ${count}`;
  // Запросы к API, подписки и т.д.
}, [count]);

// Этот код не влияет на рендер

Когда вызываются

useMemo - синхронно во время рендера:

function Component({ data }) {
  console.log('1. Start render');
  
  const computed = useMemo(() => {
    console.log('2. Computing in useMemo');
    return data.map(x => x * 2);
  }, [data]);
  
  console.log('3. Before return');
  return <div>{computed}</div>;
}

// Вывод:
// 1. Start render
// 2. Computing in useMemo
// 3. Before return

useEffect - асинхронно после рендера:

function Component({ data }) {
  console.log('1. Start render');
  
  useEffect(() => {
    console.log('2. Inside useEffect (AFTER render)');
  }, [data]);
  
  console.log('2. Before return');
  return <div>{data}</div>;
}

// Вывод:
// 1. Start render
// 2. Before return
// 2. Inside useEffect (AFTER render)

Детальное сравнение

1. Назначение

useMemo - кэширует ЗНАЧЕНИЕ (результат вычисления):

// Проблема: фильтр пересчитывается при каждом рендере
function ProductList({ products, filter }) {
  const filtered = products.filter(p => p.category === filter);
  return <List items={filtered} />;
}

// Решение: кэшировать результат фильтрации
const filtered = useMemo(
  () => products.filter(p => p.category === filter),
  [products, filter]
);

useEffect - выполняет действия (побочные эффекты):

// Запросить данные при изменении фильтра
useEffect(() => {
  fetchProducts(filter).then(setProducts);
}, [filter]);

2. Возвращаемое значение

useMemo - возвращает значение:

const cached = useMemo(() => {
  return { expensive: calculation() };
}, [deps]);

// cached - это объект, который можно использовать
const value = cached.expensive;

useEffect - ничего не возвращает (возвращает cleanup функцию):

useEffect(() => {
  const subscription = subscribe();
  
  // Cleanup функция
  return () => subscription.unsubscribe();
}, [deps]);

// useEffect не возвращает значение, только выполняет действие

3. Зависимости

useMemo - пересчитывает при изменении зависимостей:

function Component({ userId, filter }) {
  const data = useMemo(() => {
    console.log('Recalculating...');
    return filterUsers(userId, filter);
  }, [userId, filter]);
  
  // Если userId или filter изменятся - пересчитает
}

useEffect - запускается при изменении зависимостей:

function Component({ userId }) {
  useEffect(() => {
    console.log('Fetching...');
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  // Если userId изменится - запустит эффект заново
}

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

Проблема 1: Дорогое вычисление

Без оптимизации (переходит при каждом рендере):

function SearchUsers({ query }) {
  const results = users.filter(u => 
    u.name.toLowerCase().includes(query.toLowerCase())
  );
  
  return <Results items={results} />;
}
// Проблема: Results будет отренден заново каже раз, даже если query не изменился

С useMemo:

function SearchUsers({ query }) {
  const results = useMemo(
    () => users.filter(u => 
      u.name.toLowerCase().includes(query.toLowerCase())
    ),
    [query, users]
  );
  
  return <Results items={results} />;
}
// Решение: Results только переренится если query или users изменились

Проблема 2: Побочный эффект (API запрос)

Неправильное использование useMemo:

const data = useMemo(() => {
  fetch('/api/data').then(/* ... */); // ОШИБКА!
  return data;
}, []);
// Проблема: useMemo НЕ предназначен для побочных эффектов

Правильное использование useEffect:

const [data, setData] = useState(null);

useEffect(() => {
  fetch('/api/data')
    .then(r => r.json())
    .then(setData);
}, []);

return <div>{data?.title}</div>;

Проблема 3: Связь между ними

function Component({ userId, page }) {
  // 1. Запрос данных при изменении userId или page
  useEffect(() => {
    fetchUsers(userId, page).then(setUsers);
  }, [userId, page]);
  
  // 2. Кэшировать фильтрованные данные
  const filtered = useMemo(
    () => users.filter(u => u.active),
    [users]
  );
  
  // 3. Логировать изменения (побочный эффект)
  useEffect(() => {
    console.log('Data updated:', filtered);
  }, [filtered]);
  
  return <List items={filtered} />;
}

Проблемы и анти-паттерны

Неправильно: useEffect для вычисления

function Component({ data }) {
  const [computed, setComputed] = useState(null);
  
  useEffect(() => {
    setComputed(data.map(x => x * 2)); // НЕПРАВИЛЬНО!
  }, [data]);
  
  // Проблемы:
  // - Два рендера вместо одного
  // - Если data пришел из props - лишний рендер
  // - setComputed приводит к еще одному рендеру
}

Правильно: useMemo для вычисления

function Component({ data }) {
  const computed = useMemo(() => {
    return data.map(x => x * 2);
  }, [data]);
  
  // Один рендер, синхронно
  return <div>{computed}</div>;
}

Неправильно: useMemo для побочных эффектов

const cached = useMemo(() => {
  api.track('user_viewed_page'); // НЕПРАВИЛЬНО!
  return { /* data */ };
}, []);

Правильно: useEffect для побочных эффектов

useEffect(() => {
  api.track('user_viewed_page'); // Правильно
}, []);

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

// 1. Дорогие вычисления
const sorted = useMemo(
  () => [...largeArray].sort((a, b) => a.compare(b)),
  [largeArray]
);

// 2. Передача объекта как dependency child компонента
const config = useMemo(
  () => ({ theme: 'dark', fontSize: 14 }),
  []
);
return <ThemeProvider config={config} />;

// 3. Зависимость для useCallback или useEffect
const formData = useMemo(
  () => ({ name, email, phone }),
  [name, email, phone]
);

const handleSubmit = useCallback(() => {
  api.submit(formData);
}, [formData]);

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

// 1. Запросы к API
useEffect(() => {
  fetch(`/api/users/${userId}`).then(setUser);
}, [userId]);

// 2. Подписки
useEffect(() => {
  const sub = store.subscribe(updateData);
  return () => sub.unsubscribe();
}, []);

// 3. Манипуляция DOM
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

// 4. Логирование и аналитика
useEffect(() => {
  analytics.pageView(pathname);
}, [pathname]);

Сравнительная таблица

АспектuseMemouseEffect
Когда вызываетсяВо время рендераПосле рендера
ВозвращаемоеЗначениеCleanup функция
НазначениеОптимизацияПобочные эффекты
СинхронностьСинхронноАсинхронно
ЗависимостиПересчетПовторный запуск
Production использованиеОптимизацияОбычное использование

Ключевые выводы

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

Правильное использование этих хуков - признак профессиональной разработки на React.

В чем разница между useMemo и useEffect? | PrepBro