Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Mounting и Unmounting компонентов
Это критическая концепция в React и других фреймворках. Разберём полный цикл жизни компонента.
Жизненный цикл компонента
Все компоненты проходят три этапа:
1. MOUNTING (монтирование) - компонент добавляется в DOM
2. UPDATING (обновление) - компонент получает новые props/state
3. UNMOUNTING (размонтирование) - компонент удаляется из DOM
Mounting в React
useEffect для инициализации
Когда компонент монтируется, часто нужно выполнить инициализацию:
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// Выполняется ТОЛЬКО при монтировании (пустой dependency array)
useEffect(() => {
console.log('Компонент смонтирован!');
// Загружаем данные пользователя
fetchUser(userId).then(data => {
setUser(data);
setLoading(false);
});
}, []); // пустой array = выполнить один раз
if (loading) return <div>Загружаю...</div>;
return <div>{user?.name}</div>;
}
Unmounting в React
Cleanup функция в useEffect
Нужно очистить ресурсы при размонтировании:
import { useEffect } from 'react';
function ChatBox() {
useEffect(() => {
console.log('Компонент смонтирован - подключаюсь к WebSocket');
// Подключаемся при монтировании
const ws = new WebSocket('wss://chat.example.com');
ws.onopen = () => console.log('Подключены');
ws.onmessage = (event) => console.log('Сообщение:', event.data);
// Cleanup функция выполняется при размонтировании
return () => {
console.log('Компонент размонтирован - отключаюсь от WebSocket');
ws.close();
};
}, []); // выполнить один раз
return <div>Чат загружается...</div>;
}
Практические примеры
Пример 1: Event listeners
function Window() {
useEffect(() => {
const handleResize = () => {
console.log('Окно изменено:', window.innerWidth);
};
// Добавляем слушатель при монтировании
window.addEventListener('resize', handleResize);
// Удаляем при размонтировании
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Отслеживаю изменение размера</div>;
}
Пример 2: Timer/Interval
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// Запускаем интервал при монтировании
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Очищаем интервал при размонтировании
return () => {
clearInterval(interval);
};
}, []);
return <div>Прошло {seconds} секунд</div>;
}
Пример 3: Fetch и отмена запроса
function DataFetcher({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
// Используем AbortController для отмены запроса
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(`/api/data/${id}`, {
signal: controller.signal
});
const json = await response.json();
setData(json);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Ошибка:', error);
}
}
};
fetchData();
// Cleanup: отменяем запрос если компонент размонтирован
return () => {
controller.abort();
};
}, [id]); // переломаем данные если id изменился
return <div>{data ? JSON.stringify(data) : 'Загружаю...'}</div>;
}
Пример 4: LocalStorage синхронизация
function ThemeSelector() {
const [theme, setTheme] = useState('light');
// Монтирование: загружаем сохранённую тему
useEffect(() => {
const saved = localStorage.getItem('theme') || 'light';
setTheme(saved);
}, []);
// Обновление: сохраняем в localStorage
useEffect(() => {
localStorage.setItem('theme', theme);
document.documentElement.className = theme;
}, [theme]);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Текущая тема: {theme}
</button>
);
}
Dependency Array правила
Это самая частая ошибка новичков:
// ОШИБКА 1: Забыл dependency array - выполняется при каждом рендере!
useEffect(() => {
fetchData(); // ДОРОГО! Постоянно загружаем
});
// ПРАВИЛЬНО: пустой array - один раз при монтировании
useEffect(() => {
fetchData();
}, []);
// ПРАВИЛЬНО: зависит от id - при изменении id перезагружаем
useEffect(() => {
fetchData(id);
}, [id]);
// ОШИБКА 2: Забыл добавить зависимость
useEffect(() => {
fetchUser(userId); // userId не в dependencies!
}, []); // НЕПРАВИЛЬНО!
// ПРАВИЛЬНО:
useEffect(() => {
fetchUser(userId);
}, [userId]); // теперь будет перезагружаться при изменении userId
// ОШИБКА 3: Функции и объекты
function Component({ config, onCallback }) {
useEffect(() => {
// config и onCallback создаются заново при каждом рендере!
// это может вызвать бесконечный цикл
}, [config, onCallback]); // ОПАСНО!
}
// ПРАВИЛЬНО: мемоизируй зависимости
function Component({ config: rawConfig, onCallback }) {
const config = useMemo(() => rawConfig, [rawConfig]);
const callback = useCallback(onCallback, [onCallback]);
useEffect(() => {
// Теперь безопасно
}, [config, callback]);
}
Vue 3 Lifecycle Hooks
В Vue используются другие хуки:
import { onMounted, onUnmounted } from 'vue';
export default {
setup() {
// MOUNTING - компонент добавлен в DOM
onMounted(() => {
console.log('Компонент смонтирован');
// Инициализируем данные, слушателей и т.д.
});
// UNMOUNTING - компонент удален из DOM
onUnmounted(() => {
console.log('Компонент размонтирован');
// Очищаем слушатели, таймеры и т.д.
});
return {};
}
};
Чеклист для правильного unmounting
При размонтировании нужно очистить:
// ✓ Event listeners
window.removeEventListener('resize', handler);
dom.removeEventListener('click', handler);
// ✓ Timers
clearInterval(intervalId);
clearTimeout(timeoutId);
// ✓ Subscriptions
unsubscribe();
subscription.unsubscribe();
// ✓ WebSocket/Socket.io
ws.close();
socket.disconnect();
// ✓ HTTP запросы (AbortController)
controller.abort();
// ✓ Observers
intersectionObserver.disconnect();
resizeObserver.disconnect();
// ✓ Всё что может привести к утечке памяти
delete ref.listener;
cache.clear();
Полный пример: Chat компонент
function Chat({ roomId, username }) {
const [messages, setMessages] = useState([]);
const [socket, setSocket] = useState(null);
// Монтирование: подключаемся к комнате
useEffect(() => {
console.log(`Монтирование: подключаюсь к комнате ${roomId}`);
const ws = new WebSocket(`wss://chat.example.com/${roomId}`);
ws.onopen = () => {
console.log('Подключен к сокету');
ws.send(JSON.stringify({ type: 'join', username }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
ws.onerror = (error) => {
console.error('Ошибка сокета:', error);
};
setSocket(ws);
// Размонтирование: отключаемся от комнаты
return () => {
console.log('Размонтирование: отключаюсь от комнаты');
ws.send(JSON.stringify({ type: 'leave', username }));
ws.close();
};
}, [roomId, username]); // переподключаемся если roomId или username изменились
const sendMessage = (text) => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({
type: 'message',
text,
username
}));
}
};
return (
<div className="chat">
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className="message">
<strong>{msg.username}:</strong> {msg.text}
</div>
))}
</div>
<ChatInput onSend={sendMessage} />
</div>
);
}
Debugging Mounting/Unmounting
// Добавь логирование для отладки
function Component() {
useEffect(() => {
console.log('%c Mounting', 'color: green; font-weight: bold');
return () => {
console.log('%c Unmounting', 'color: red; font-weight: bold');
};
}, []);
return <div>Смотри в консоль при монтировании/размонтировании</div>;
}
Итого
Monting и Unmounting — критический аспект управления компонентами:
- Mounting - инициализируй данные, слушатели, подписки
- Unmounting - очищай всё что инициализировал
- Используй cleanup функции в useEffect
- Правильно настраивай dependency arrays
- Проверяй утечки памяти в DevTools
Неправильное управление жизненным циклом приводит к утечкам памяти, утечкам сокетов и неожиданному поведению.