Типизация useEffect
useEffect редко требует явной типизации — типы выводятся. Но есть нюансы.
Базовый эффект
useEffect(() => {
console.log('mount');
}, []);
Тип не нужен.
Функция очистки
useEffect(() => {
const id = setInterval(() => {}, 1000);
return () => clearInterval(id);
}, []);
Возвращаемый тип — void | (() => void). Можно вернуть undefined или функцию.
Async-эффект
useEffect не принимает async-функцию напрямую:
// Ошибка: возвращает Promise
useEffect(async () => {
const data = await fetchData();
}, []);
Решение — определить async-функцию внутри:
useEffect(() => {
async function load() {
const data = await fetchData();
setData(data);
}
load();
}, []);
С AbortController
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const res = await fetch(url, { signal: controller.signal });
setData(await res.json());
} catch (e) {
if (e instanceof Error && e.name !== 'AbortError') {
setError(e);
}
}
}
load();
return () => controller.abort();
}, [url]);
Тип ошибки
В catch ошибка имеет тип unknown:
try {
await fetchData();
} catch (e: unknown) {
if (e instanceof Error) {
setError(e.message);
} else {
setError('Неизвестная ошибка');
}
}
С useUnknownInCatchVariables: false (не рекомендуется) — any.
Зависимости
useEffect(() => {
//
}, [userId, filters]);
filters — объект. Если создаётся в рендере — эффект срабатывает каждый рендер. Решение — useMemo.
Event listener
useEffect(() => {
function handleKey(e: KeyboardEvent) {
if (e.key === 'Escape') close();
}
document.addEventListener('keydown', handleKey);
return () => document.removeEventListener('keydown', handleKey);
}, [close]);
KeyboardEvent — нативный DOM-тип, не React-тип.
IntersectionObserver
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) setVisible(true);
});
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
ResizeObserver
useEffect(() => {
const observer = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
setSize({ width, height });
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
local storage
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
Итоги
- Типизация
useEffectобычно не нужна - Async-функция — внутри
AbortControllerдля отменыcatch (e: unknown)— проверяй- Нативные типы для DOM-событий
useMemoдля объектов в зависимостях