Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл React компонента через хуки
В функциональных компонентах нет методов жизненного цикла вроде componentDidMount. Вместо этого используются хуки, особенно useEffect. Рассмотрю, как реализовать весь жизненный цикл.
1. Сопоставление методов класса и хуков
// Классовый компонент (старый подход)
class Counter extends React.Component {
componentDidMount() {
// Выполнится один раз при монтировании
console.log("Компонент смонтирован");
}
componentDidUpdate(prevProps, prevState) {
// Выполнится после каждого обновления
console.log("Компонент обновился");
}
componentWillUnmount() {
// Выполнится при размонтировании
console.log("Компонент размонтирован");
}
render() {
return <div>{this.state.count}</div>;
}
}
// Функциональный компонент с хуками (новый подход)
function Counter() {
const [count, setCount] = useState(0);
// Эквивалент componentDidMount (только монтирование)
useEffect(() => {
console.log("Компонент смонтирован");
}, []); // Пустой массив зависимостей!
// Эквивалент componentDidUpdate (после каждого обновления)
useEffect(() => {
console.log("Компонент обновился или count изменился");
}); // Нет массива зависимостей или он не пуст
// Эквивалент componentWillUnmount (перед размонтированием)
useEffect(() => {
return () => {
console.log("Компонент размонтирован");
};
}, []);
return <div>{count}</div>;
}
2. useEffect подробно: монтирование, обновление, размонтирование
import { useEffect, useState } from "react";
function DataFetcher({ userId }: { userId: string }) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 1. МОНТИРОВАНИЕ: выполнить один раз при загрузке компонента
useEffect(() => {
console.log("Компонент смонтирован, загружаем данные");
setIsLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setData(data))
.catch(err => setError(err.message))
.finally(() => setIsLoading(false));
// Очистка при размонтировании (return в useEffect)
return () => {
console.log("Компонент размонтируется, отменяем запрос");
// Отменить fetch, очистить таймеры и т.д.
};
}, []); // Пустой массив = выполнить один раз при монтировании
// 2. ОБНОВЛЕНИЕ: выполнить когда userId изменился
useEffect(() => {
console.log(`userId изменился на ${userId}, перезагружаем`);
// Логика обновления при изменении userId
}, [userId]); // userId в зависимостях = выполнить при его изменении
// 3. КАЖДЫЙ РЕНДЕР: без зависимостей выполнится всегда
// (Не рекомендуется! Может вызвать бесконечные обновления)
useEffect(() => {
console.log("Это выполнится после КАЖДОГО рендера!");
}); // Нет массива зависимостей
return (
<div>
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error}</p>}
{data && <p>{JSON.stringify(data)}</p>}
</div>
);
}
3. Полный жизненный цикл компонента
function LifecycleExample({ isVisible }: { isVisible: boolean }) {
const [count, setCount] = useState(0);
const [status, setStatus] = useState("initialized");
// ФАЗА 1: МОНТИРОВАНИЕ (один раз)
useEffect(() => {
console.log("1. MOUNTED - компонент появился в DOM");
setStatus("mounted");
// Инициализация: подписаться на события, запустить таймер и т.д.
const timer = setInterval(() => {
console.log("Таймер работает");
}, 5000);
// ФАЗА 4: РАЗМОНТИРОВАНИЕ (очистка)
return () => {
console.log("4. UNMOUNTED - компонент исчезает из DOM");
clearInterval(timer); // Очистить таймер
setStatus("unmounted");
};
}, []);
// ФАЗА 2: ОБНОВЛЕНИЕ (когда isVisible изменяется)
useEffect(() => {
if (isVisible) {
console.log("2a. UPDATED - компонент видимый");
setStatus("visible");
} else {
console.log("2b. UPDATED - компонент скрытый");
setStatus("hidden");
}
// ФАЗА 3: ОЧИСТКА перед следующим обновлением (optional)
return () => {
console.log("3. CLEANUP before next update - очищаем старые ресурсы");
};
}, [isVisible]); // Зависимость на isVisible
// ФАЗА 2.2: ОБНОВЛЕНИЕ (когда count изменяется)
useEffect(() => {
console.log(`2. COUNT UPDATED - новое значение: ${count}`);
// Отправить аналитику
// Обновить заголовок страницы
document.title = `Count: ${count}`;
return () => {
console.log("Очистка перед следующим обновлением count");
};
}, [count]);
// ФАЗА 2.3: ВЫПОЛНИТЬ ПОСЛЕ КАЖДОГО РЕНДЕРА (без зависимостей)
// Обычно избегают! Может привести к баг-борам
useEffect(() => {
console.log("Это выполнится после КАЖДОГО рендера");
// Опасно! Может вызвать бесконечный цикл
});
return (
<div>
<p>Status: {status}</p>
<p>Count: {count}</p>
<p>isVisible: {isVisible ? "да" : "нет"}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// Использование:
// <LifecycleExample isVisible={true} />
// В консоли:
// 1. MOUNTED
// 2a. UPDATED (visible)
// 2. COUNT UPDATED (0)
// ... click button ...
// 2. COUNT UPDATED (1)
// ... hide component ...
// 3. CLEANUP
// 2b. UPDATED (hidden)
// ... unmount ...
// 4. UNMOUNTED
4. Общие проблемы и как их избежать
// ПРОБЛЕМА 1: Бесконечный цикл обновлений
function BadComponent() {
const [count, setCount] = useState(0);
// БЕЗ зависимостей выполняется после каждого рендера
useEffect(() => {
setCount(count + 1); // Вызывает новый рендер -> новый useEffect
}); // Бесконечный цикл!
return <div>{count}</div>;
}
// РЕШЕНИЕ: добавить зависимость или пустой массив
function GoodComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1); // Выполнится один раз при монтировании
}, []); // Пустой массив = только при монтировании
return <div>{count}</div>;
}
// ПРОБЛЕМА 2: Утечка памяти - забыли очистить таймер
function BadTimer() {
useEffect(() => {
const interval = setInterval(() => {
console.log("tick");
}, 1000); // Таймер будет работать даже после размонтирования!
}, []);
}
// РЕШЕНИЕ: вернуть функцию очистки
function GoodTimer() {
useEffect(() => {
const interval = setInterval(() => {
console.log("tick");
}, 1000);
return () => {
clearInterval(interval); // Очищаем при размонтировании
};
}, []);
}
// ПРОБЛЕМА 3: Зависимость не включена
function BadDependency({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, []); // userId не в зависимостях!
// Будет грузить только первого пользователя
return <div>{user?.name}</div>;
}
// РЕШЕНИЕ: добавить зависимость
function GoodDependency({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, [userId]); // Теперь перезагружает при изменении userId
return <div>{user?.name}</div>;
}
5. Практический пример: полный цикл жизни
interface WindowSize {
width: number;
height: number;
}
function ResponsiveComponent() {
const [windowSize, setWindowSize] = useState<WindowSize>({
width: window.innerWidth,
height: window.innerHeight
});
// Монтирование + Обновление + Размонтирование
useEffect(() => {
// МОНТИРОВАНИЕ: подписаться на событие
const handleResize = () => {
console.log("Окно изменило размер");
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
console.log("MOUNTED: добавляю слушатель resize");
window.addEventListener("resize", handleResize);
// РАЗМОНТИРОВАНИЕ: отписаться от события
return () => {
console.log("UNMOUNTED: удаляю слушатель resize");
window.removeEventListener("resize", handleResize);
};
}, []); // Выполнить только при монтировании/размонтировании
const isMobile = windowSize.width < 768;
return (
<div>
<p>Window size: {windowSize.width}x{windowSize.height}</p>
<p>{isMobile ? "Mobile" : "Desktop"}</p>
</div>
);
}
6. Кастомные хуки для повтора жизненного цикла
// Кастомный хук для выполнения кода при монтировании
function useMount(callback: () => void | (() => void)) {
useEffect(callback, []);
}
// Кастомный хук для выполнения кода при размонтировании
function useUnmount(callback: () => void) {
useEffect(() => callback, []);
}
// Кастомный хук для отслеживания изменений
function useDidUpdate(callback: () => void, dependencies: any[]) {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
callback();
}, dependencies);
}
// Использование:
function MyComponent() {
useMount(() => {
console.log("Компонент смонтирован");
return () => console.log("Компонент размонтирован");
});
useDidUpdate(() => {
console.log("Произошло обновление");
}, [someDependency]);
return <div>Hello</div>;
}
Рекомендация
Жизненный цикл через хуки:
- useEffect(fn, []) — монтирование
- useEffect(fn, [deps]) — обновление при изменении зависимостей
- useEffect(() => cleanup, []) — размонтирование
- return () => {} в useEffect — очистка ресурсов
- Всегда добавляй зависимости! — иначе будут баги и утечки памяти