← Назад к вопросам
Что будет, если в UseEffect вернуть другую Callback функцию?
1.2 Junior🔥 151 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что будет, если в useEffect вернуть другую Callback функцию?
В React useEffect может возвращать функцию очистки (cleanup function), которая выполняется перед следующим эффектом или при размонтировании компонента. Если вернуть "другую" callback функцию, это может привести к неожиданному поведению.
Основы useEffect cleanup
useEffect(() => {
// Это побочный эффект
const subscription = eventBus.subscribe('event', handler);
console.log('Эффект выполнен');
// Это функция очистки
return () => {
subscription.unsubscribe();
console.log('Очистка выполнена');
};
}, []);
Порядок выполнения:
- Компонент монтируется
- Выполняется основное тело useEffect
- Компонент размонтируется или зависимости меняются
- Выполняется функция очистки
- Если есть новые зависимости, выполняется новый эффект
Что произойдет при возврате другой функции?
Вариант 1: Разные функции на каждый рендер
function MyComponent() {
useEffect(() => {
console.log('Эффект');
// Возвращаем новую функцию КАЖДЫЙ раз
return () => {
console.log('Очистка 1');
};
}); // Нет зависимостей!
return <div>Компонент</div>;
}
// Что произойдет:
// 1. Рендер 1 -> Эффект -> (переменные меняются, компонент перерисовывается)
// 2. Рендер 2 -> Очистка 1 (старая функция) -> Эффект -> (переменные меняются)
// 3. Рендер 3 -> Очистка 2 (другая функция) -> Эффект
// ...
// Это запускается КАЖДЫЙ рендер!
Вариант 2: Условный возврат
function ConditionalCleanup({ condition }) {
useEffect(() => {
console.log('Эффект');
if (condition) {
return () => {
console.log('Очистка А');
};
} else {
return () => {
console.log('Очистка Б');
};
}
}, [condition]);
}
// Это работает, но может быть запутанным
// Если condition меняется:
// 1. Выполняется очистка старого условия
// 2. Выполняется новый эффект с новой функцией очистки
Вариант 3: Возврат не функции
function BadCleanup() {
useEffect(() => {
console.log('Эффект');
// ОШИБКА: возвращаем не функцию
return "это не функция"; // TypeError!
}, []);
return <div>Компонент</div>;
}
// React ожидает функцию или undefined
// Получит TypeError: cleanup is not a function
Правильное использование cleanup
function GoodExample({ id }) {
useEffect(() => {
let mounted = true;
const fetchData = async () => {
const data = await fetch(`/api/data/${id}`);
if (mounted) {
// Обновляем state только если компонент все еще на экране
setData(data);
}
};
fetchData();
// Возвращаем ОДНУ функцию очистки
return () => {
mounted = false;
};
}, [id]);
return <div>{data}</div>;
}
Практические примеры проблем
Проблема 1: Утечка событий
function WindowResizeListener() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
console.log('Resize detected');
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// НЕПРАВИЛЬНО - разные функции очистки
return () => {
// Это другая функция! removeEventListener не сработает!
window.removeEventListener('resize', () => {
console.log('Resize detected');
setWidth(window.innerWidth);
});
};
}, []);
return <div>Width: {width}</div>;
}
// Проблема: обработчик события остается в памяти!
// Каждый раз при resize добавляется новый обработчик
Решение:
function WindowResizeListener() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// ПРАВИЛЬНО - та же функция
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Width: {width}</div>;
}
Проблема 2: Утечка памяти в интервалах
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// НЕПРАВИЛЬНО
return () => {
clearInterval(() => { // Передаем функцию вместо ID!
setCount(c => c + 1);
});
};
}, []);
return <div>{count}</div>;
}
// Интервал никогда не очищается!
// Утечка памяти при размонтировании
Решение:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// ПРАВИЛЬНО
return () => {
clearInterval(interval);
};
}, []);
return <div>{count}</div>;
}
Проблема 3: Асинхронные запросы
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data); // Race condition!
};
fetchUser();
// НЕПРАВИЛЬНО - разные функции очистки
return () => {
// Эта функция не может отменить fetch!
};
}, [userId]);
return <div>{user?.name}</div>;
}
// Если userId меняется быстро:
// 1. Fetch для userId=1 началась
// 2. userId меняется на 2
// 3. Fetch для userId=2 началась
// 4. Fetch 2 завершилась первой -> setUser(user2)
// 5. Fetch 1 завершилась -> setUser(user1) ПЕРЕЗАПИСЫВАЕТ!
Решение с AbortController:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchUser = async () => {
try {
const response = await fetch(
`/api/users/${userId}`,
{ signal: controller.signal }
);
const data = await response.json();
setUser(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error(error);
}
}
};
fetchUser();
// ПРАВИЛЬНО - отменяем запрос
return () => {
controller.abort();
};
}, [userId]);
return <div>{user?.name}</div>;
}
Типичные ошибки
// ОШИБКА 1: Возврат Promise
useEffect(() => {
return async () => {
// Это async функция, не подойдет
await cleanup();
};
}, []);
// ОШИБКА 2: Возврат значения
useEffect(() => {
return 123; // TypeError
}, []);
// ОШИБКА 3: Разные функции
useEffect(() => {
const listener = () => console.log('event');
document.addEventListener('click', listener);
// ОШИБКА: разная функция!
return () => {
document.removeEventListener('click', () => {
console.log('event');
});
};
}, []);
Правильные паттерны
Паттерн 1: Простая очистка
useEffect(() => {
const handler = () => { /* ... */ };
element.addEventListener('event', handler);
return () => {
element.removeEventListener('event', handler);
};
}, []);
Паттерн 2: Множественная очистка
useEffect(() => {
const handler1 = () => { /* ... */ };
const handler2 = () => { /* ... */ };
element.addEventListener('click', handler1);
window.addEventListener('resize', handler2);
return () => {
element.removeEventListener('click', handler1);
window.removeEventListener('resize', handler2);
};
}, []);
Паттерн 3: Условная очистка
useEffect(() => {
let subscription = null;
if (isActive) {
subscription = eventBus.subscribe('event', handler);
}
return () => {
if (subscription) {
subscription.unsubscribe();
}
};
}, [isActive]);
Чеклист правильности
Функция очистки должна:
[] Быть функцией (не Promise, не число)
[] Быть ОДНОЙ и ТОЙ ЖЕ функцией для одних зависимостей
[] Очищать ровно то, что создавал эффект
[] Быть заполнена до размонтирования или нового эффекта
[] Не выбрасывать ошибки
Выводы
- useEffect должен возвращать функцию или undefined
- Если возвращаешь функцию очистки, она должна быть синхронной
- Функция очистки должна удалять именно то, что создал эффект
- Используй AbortController для отмены асинхронных операций
- Избегай создания новых функций в return - используй переменные из области видимости
- Не забывай про утечки памяти - обработчики, интервалы, подписки
Правильное использование cleanup функций — это критично для предотвращения утечек памяти и race condition в React приложениях.