Какие знаешь примеры использования cleanup у useEffect?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Cleanup функция в useEffect: примеры использования
Что такое cleanup
Cleanup функция (функция очистки) — это функция, которую ты возвращаешь из useEffect. Она вызывается когда компонент размонтируется или перед выполнением эффекта в следующий раз. Это критически важно для предотвращения утечек памяти и побочных эффектов.
useEffect(() => {
// Эффект — выполняется при монтировании
return () => {
// Cleanup — выполняется при размонтировании или перед следующим эффектом
}
}, [dependencies])
Практические примеры
1. Отписка от Event Listeners
function Window() {
const [windowSize, setWindowSize] = React.useState({
width: window.innerWidth,
height: window.innerHeight
})
React.useEffect(() => {
// Подписываемся на resize
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', handleResize)
// Cleanup: отписываемся от события
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return <div>Width: {windowSize.width}, Height: {windowSize.height}</div>
}
2. Отмена HTTP запросов (AbortController)
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null)
const [loading, setLoading] = React.useState(true)
const [error, setError] = React.useState(null)
React.useEffect(() => {
// Создаем AbortController для отмены запроса
const abortController = new AbortController()
setLoading(true)
fetch(`/api/users/${userId}`, {
signal: abortController.signal
})
.then(res => res.json())
.then(data => {
setUser(data)
setLoading(false)
})
.catch(err => {
if (err.name !== 'AbortError') {
setError(err)
setLoading(false)
}
})
// Cleanup: отменяем запрос если компонент размонтируется
return () => {
abortController.abort()
}
}, [userId])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>{user?.name}</div>
}
3. Отмена setTimeout
function Notification() {
const [visible, setVisible] = React.useState(true)
React.useEffect(() => {
if (!visible) return
// Скрываем уведомление через 5 секунд
const timeoutId = setTimeout(() => {
setVisible(false)
}, 5000)
// Cleanup: отменяем таймер если компонент размонтируется раньше
return () => {
clearTimeout(timeoutId)
}
}, [visible])
return visible ? <div className="notification">Message!</div> : null
}
4. Отмена setInterval
function Timer() {
const [seconds, setSeconds] = React.useState(0)
React.useEffect(() => {
// Увеличиваем счетчик каждую секунду
const intervalId = setInterval(() => {
setSeconds(prev => prev + 1)
}, 1000)
// Cleanup: останавливаем интервал
return () => {
clearInterval(intervalId)
}
}, [])
return <div>Time: {seconds}s</div>
}
5. Отписка от WebSocket
function ChatMessages() {
const [messages, setMessages] = React.useState([])
React.useEffect(() => {
const socket = new WebSocket('wss://api.example.com/chat')
socket.onopen = () => {
console.log('WebSocket connected')
}
socket.onmessage = (event) => {
const message = JSON.parse(event.data)
setMessages(prev => [...prev, message])
}
socket.onerror = (error) => {
console.error('WebSocket error:', error)
}
// Cleanup: закрываем соединение
return () => {
if (socket.readyState === WebSocket.OPEN) {
socket.close()
}
}
}, [])
return (
<div>
{messages.map(msg => <p key={msg.id}>{msg.text}</p>)}
</div>
)
}
6. Отписка от подписок (observer pattern)
function LocationTracker() {
const [location, setLocation] = React.useState(null)
React.useEffect(() => {
// Получаем текущую позицию
const watchId = navigator.geolocation.watchPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude
})
},
(error) => {
console.error('Geolocation error:', error)
}
)
// Cleanup: отменяем отслеживание
return () => {
navigator.geolocation.clearWatch(watchId)
}
}, [])
return location ? (
<div>Lat: {location.latitude}, Lon: {location.longitude}</div>
) : (
<div>Waiting for location...</div>
)
}
7. Отписка от подписок в React Context или Redux
function UserStatus() {
const [status, setStatus] = React.useState('offline')
React.useEffect(() => {
// Подписываемся на изменения статуса пользователя
const unsubscribe = userStatusStore.subscribe((newStatus) => {
setStatus(newStatus)
})
// Cleanup: отписываемся от магазина
return () => {
unsubscribe()
}
}, [])
return <div>Status: {status}</div>
}
8. Очистка DOM манипуляций
function FocusInput() {
const inputRef = React.useRef(null)
React.useEffect(() => {
// Устанавливаем фокус на input
inputRef.current?.focus()
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
inputRef.current?.blur()
}
}
document.addEventListener('keydown', handleKeyDown)
// Cleanup: удаляем listener и сбрасываем стили
return () => {
document.removeEventListener('keydown', handleKeyDown)
inputRef.current?.blur()
}
}, [])
return <input ref={inputRef} type="text" />
}
9. Отписка от Intersection Observer
function LazyImage() {
const [isVisible, setIsVisible] = React.useState(false)
const imageRef = React.useRef(null)
React.useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
// Перестаем наблюдать после первого пересечения
observer.unobserve(entry.target)
}
})
if (imageRef.current) {
observer.observe(imageRef.current)
}
// Cleanup: отписываемся от наблюдателя
return () => {
observer.disconnect()
}
}, [])
return (
<img
ref={imageRef}
src={isVisible ? '/lazy-image.jpg' : '/placeholder.jpg'}
alt="Lazy loaded"
/>
)
}
10. Очистка state при выполнении нового эффекта
function AutoComplete() {
const [query, setQuery] = React.useState('')
const [results, setResults] = React.useState([])
const [loading, setLoading] = React.useState(false)
React.useEffect(() => {
if (!query) {
setResults([])
return
}
setLoading(true)
const timeoutId = setTimeout(() => {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data))
.finally(() => setLoading(false))
}, 300)
// Cleanup: отменяем текущий таймер и результаты если query изменится
return () => {
clearTimeout(timeoutId)
setResults([])
setLoading(false)
}
}, [query])
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{loading && <div>Loading...</div>}
{results.map(item => <p key={item.id}>{item.name}</p>)}
</div>
)
}
Когда cleanup вызывается
useEffect(() => {
console.log('Effect runs')
return () => console.log('Cleanup runs')
}, [dep])
Сценарии:
- Компонент размонтируется — cleanup всегда вызывается
- Dependency изменяется — cleanup вызывается перед новым эффектом
- Компонент переиспользуется с новыми props — cleanup вызывается перед эффектом
Порядок выполнения
// Первый рендер:
useEffect(() => {
console.log('1. Effect (dep=5)')
return () => console.log('cleanup')
}, [5])
// Dependency изменился на 10:
// Вывод:
// 1. cleanup
// 2. Effect (dep=10)
// Размонтирование:
// cleanup
Best Practices
Всегда очищай ресурсы:
// Плохо: утечка памяти
React.useEffect(() => {
window.addEventListener('resize', handleResize)
// Забыли удалить listener
}, [])
// Хорошо: очищаем ресурс
React.useEffect(() => {
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
Cleanup — ответственность за правильное завершение асинхронных операций:
- Отмена fetch запросов
- Очистка таймеров
- Отписка от обозревателей
- Закрытие соединений
- Удаление слушателей событий
Итого
Cleanup функция в useEffect:
- Вызывается при размонтировании компонента
- Вызывается перед выполнением эффекта если зависимости изменились
- Предотвращает утечки памяти
- Отменяет незавершенные асинхронные операции
- Критична для надежного и безопасного кода