Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между 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]);
Сравнительная таблица
| Аспект | useMemo | useEffect |
|---|---|---|
| Когда вызывается | Во время рендера | После рендера |
| Возвращаемое | Значение | Cleanup функция |
| Назначение | Оптимизация | Побочные эффекты |
| Синхронность | Синхронно | Асинхронно |
| Зависимости | Пересчет | Повторный запуск |
| Production использование | Оптимизация | Обычное использование |
Ключевые выводы
- useMemo - для кэширования вычисленных значений
- useEffect - для выполнения побочных эффектов
- useMemo работает во время рендера, useEffect после
- Не смешивай эффекты в useMemo и вычисления в useEffect
- Не переоптимизируй - используй useMemo только когда нужно
Правильное использование этих хуков - признак профессиональной разработки на React.