Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
UseEffect: синхронно или асинхронно
useEffect выполняется асинхронно, но с важными нюансами. Понимание этого критично для правильной работы с побочными эффектами в React.
Как работает useEffect
Этап 1: Рендер компонента (синхронно)
function MyComponent() {
// Это выполняется синхронно при рендере
const [count, setCount] = useState(0)
console.log('Render: count =', count)
return <div>{count}</div>
}
Этап 2: Обновление DOM (синхронно)
// React обновляет DOM
// Это происходит после рендера, но ДО useEffect
Этап 3: Выполнение useEffect (асинхронно)
function MyComponent() {
const [count, setCount] = useState(0)
// Это выполняется ПОСЛЕ рендера и обновления DOM
useEffect(() => {
console.log('useEffect: count =', count)
}, [count])
return <div>{count}</div>
}
Порядок выполнения
1. Рендер функции компонента
2. Обновление DOM
3. Запуск useEffect (асинхронно)
4. Очистка от старого useEffect
5. Выполнение нового useEffect
Пример временной последовательности
function Counter() {
const [count, setCount] = useState(0)
console.log('1. Render') // Выполняется ПЕРВЫМ (синхронно)
useEffect(() => {
console.log('3. useEffect') // Выполняется ТРЕТЬИМ (асинхронно)
return () => {
console.log('4. useEffect cleanup') // Перед следующим эффектом
}
}, [count])
// Клик на кнопку
const handleClick = () => {
console.log('2. Click handler') // Выполняется ВТОРЫМ
setCount(count + 1)
}
return <button onClick={handleClick}>Count: {count}</button>
}
// Вывод при клике:
// 1. Render
// 2. Click handler
// 4. useEffect cleanup (от предыдущего эффекта)
// 1. Render (повторный рендер)
// 3. useEffect (новый эффект)
Когда useEffect выполняется
После рендера, но асинхронно:
function Example() {
// СИНХРОННО при рендере
const [data, setData] = useState(null)
console.log('Render, data =', data)
// АСИНХРОННО после рендера
useEffect(() => {
console.log('Effect, fetching data...')
fetch('/api/data').then(response => {
setData(response.data)
})
}, [])
return <div>{data ? 'Loaded' : 'Loading'}</div>
}
// Порядок логов:
// 1. Render, data = null
// 2. Effect, fetching data...
// (когда пришли данные)
// 3. Render, data = { ... }
useLayoutEffect - синхронная альтернатива
Если нужно выполнить что-то синхронно, используй useLayoutEffect:
function Example() {
const ref = useRef()
// useEffect - выполняется асинхронно (ПОСЛЕ отрисовки)
useEffect(() => {
console.log('useEffect: rect =', ref.current?.getBoundingClientRect())
}, [])
// useLayoutEffect - выполняется синхронно (ДО отрисовки)
useLayoutEffect(() => {
console.log('useLayoutEffect: rect =', ref.current?.getBoundingClientRect())
}, [])
return <div ref={ref}>Element</div>
}
// Порядок:
// 1. Рендер
// 2. useLayoutEffect (синхронно, ДО отрисовки)
// 3. Браузер отрисовывает пиксели
// 4. useEffect (асинхронно, ПОСЛЕ отрисовки)
Графическое представление
┌─────────────────────────────────────────┐
│ Компонент рендерится (синхронно) │
│ console.log('Render') │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ DOM обновляется │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ useLayoutEffect выполняется (синхронно) │
│ Может произойти еще один рендер │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Браузер отрисовывает на экран │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ useEffect выполняется (асинхронно) │
└─────────────────────────────────────────┘
Практические примеры
Правильно: Загрузка данных в useEffect
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(data => {
setUser(data)
setLoading(false)
})
.catch(err => {
setError(err)
setLoading(false)
})
}, [userId])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>User: {user.name}</div>
}
Правильно: Очистка слушателей события
function WindowResize() {
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>
}
Неправильно: useEffect с пустым массивом зависимостей
function BadExample() {
// НЕПРАВИЛЬНО - dependency array важен
useEffect(() => {
console.log('Effect') // Выполнится один раз при монтировании
}) // ❌ Забыли массив зависимостей
// ПРАВИЛЬНО
useEffect(() => {
console.log('Effect')
}, []) // ✅ Выполнится один раз
}
Race condition в useEffect
function SearchUsers({ query }) {
const [results, setResults] = useState([])
useEffect(() => {
// Поиск асинхронный
fetch(`/api/search?q=${query}`)
.then(response => response.json())
.then(data => setResults(data))
}, [query])
return (
<ul>
{results.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
)
}
// Если пользователь быстро меняет query:
// 1. Поиск по 'react' - запрос отправлен
// 2. Поиск по 'react-router' - запрос отправлен
// 3. Ответ по 'react' приходит позже
// 4. Результаты обновляются на 'react' (неправильно!)
Решение с AbortController:
function SearchUsers({ query }) {
const [results, setResults] = useState([])
useEffect(() => {
const controller = new AbortController()
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(response => response.json())
.then(data => setResults(data))
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err)
}
})
return () => controller.abort() // Отменяем при новом запросе
}, [query])
return (
<ul>
{results.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
)
}
Таблица сравнения
┌──────────────┬─────────────────────┬────────────────────┐
│ Свойство │ useEffect │ useLayoutEffect │
├──────────────┼─────────────────────┼────────────────────┤
│ Выполнение │ Асинхронно │ Синхронно │
│ Когда │ После рендера │ После рендера, │
│ │ и отрисовки │ перед отрисовкой │
│ Использование│ Данные, события │ Измерения, layout │
│ Производство │ Безопасен │ Может заблокировать│
└──────────────┴─────────────────────┴────────────────────┘
Вывод
useEffect выполняется асинхронно:
- Компонент рендерится синхронно
- DOM обновляется
- useEffect запускается асинхронно (в конце цикла рендера)
- Браузер отрисовывает результат
Это сделано для производительности - асинхронное выполнение не блокирует отрисовку. Используй useLayoutEffect только если действительно нужно выполнить что-то синхронно перед отрисовкой.