← Назад к вопросам
Что срабатывает раньше useEffect или useLayoutEffect?
1.8 Middle🔥 171 комментариев
#React#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# useEffect vs useLayoutEffect: Порядок выполнения
Это один из самых частых вопросов на собеседованиях и очень важно понимать разницу между этими двумя хуками. Порядок выполнения критически влияет на производительность и поведение приложения.
Краткий ответ
useLayoutEffect срабатывает РАНЬШЕ, чем useEffect.
Порядок:
- React обновляет DOM
- useLayoutEffect срабатывает (синхронно)
- Браузер рисует (отображает изменения)
- useEffect срабатывает (асинхронно)
Детальное объяснение с визуализацией
function EffectComparison() {
console.log('1. Рендер компонента');
React.useLayoutEffect(() => {
console.log('2. useLayoutEffect (СИНХРОННО, до отрисовки)');
}, []);
React.useEffect(() => {
console.log('4. useEffect (АСИНХРОННО, после отрисовки)');
}, []);
console.log('3. Компонент готов к отрисовке (но ещё не нарисован)');
return <div>Компонент</div>;
}
// Вывод в консоли:
// 1. Рендер компонента
// 3. Компонент готов к отрисовке
// 2. useLayoutEffect (СИНХРОННО)
// [Браузер отрисовывает DOM]
// 4. useEffect (АСИНХРОННО)
Временная шкала React
┌─────────────────────────────────────────────────────────┐
│ 1. React рендерит компонент (в памяти) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. React обновляет DOM │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. ⚡ useLayoutEffect срабатывает (БЛОКИРУЮЩИЙ) │
│ Здесь браузер НЕ рисует, вы можете измерять DOM │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 4. Браузер отрисовывает (Paint) новые изменения │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 5. useEffect срабатывает (НЕБЛОКИРУЮЩИЙ) │
│ Здесь браузер уже отобразил изменения │
└─────────────────────────────────────────────────────────┘
Практические примеры
Пример 1: Флиш контента (Flash)
Это классический пример, почему важно различать эти хуки:
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef(null);
// ❌ ПЛОХО: useEffect
React.useEffect(() => {
// Пользователь видит: 0 → 5
// Потому что DOM уже нарисован, потом мы обновляем
if (count === 0) {
setCount(5);
}
}, [count]);
return (
<div>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// ✅ ХОРОШО: useLayoutEffect
function CounterFixed() {
const [count, setCount] = React.useState(0);
React.useLayoutEffect(() => {
// Пользователь видит: сразу 5
// Потому что обновление произойдёт ДО отрисовки
if (count === 0) {
setCount(5);
}
}, [count]);
return (
<div>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Пример 2: Измерение размеров элемента
function MeasureElement() {
const ref = React.useRef(null);
const [height, setHeight] = React.useState(0);
// ✅ ПРАВИЛЬНО: useLayoutEffect
// Измеряем ПЕРЕД отрисовкой
React.useLayoutEffect(() => {
if (ref.current) {
// В этот момент DOM обновлён, но браузер ещё не рисовал
setHeight(ref.current.offsetHeight);
}
}, []);
return (
<div
ref={ref}
style={{ height: height > 0 ? height : 'auto' }}
>
Content
</div>
);
}
Пример 3: Синхронизация с внешней библиотекой
function ChartComponent() {
const containerRef = React.useRef(null);
const chartRef = React.useRef(null);
// ✅ ПРАВИЛЬНО: useLayoutEffect
// Дорогие вычисления размеров должны быть ДО отрисовки
React.useLayoutEffect(() => {
if (containerRef.current) {
const width = containerRef.current.clientWidth;
const height = containerRef.current.clientHeight;
// Инициализируем график с правильными размерами
chartRef.current = new Chart(containerRef.current, {
width,
height,
});
}
return () => {
if (chartRef.current) {
chartRef.current.destroy();
}
};
}, []);
return <div ref={containerRef} style={{ width: '100%', height: 400 }} />;
}
Когда использовать что?
Используй useEffect (в 99% случаев)
// Загрузка данных
React.useEffect(() => {
fetchData().then(setData);
}, []);
// Подписка на события
React.useEffect(() => {
const handler = () => { };
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
// Отправка аналитики
React.useEffect(() => {
sendAnalytics('page_viewed');
}, [pathname]);
// Обновление document.title
React.useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
Используй useLayoutEffect (редко, в специфических случаях)
// Измерение размеров ПЕРЕД отрисовкой
React.useLayoutEffect(() => {
const width = element.offsetWidth;
setSize(width);
}, []);
// Позиционирование tooltip/popover
React.useLayoutEffect(() => {
const pos = calculatePosition();
setPosition(pos);
}, [trigger]);
// Синхронизация с DOM-нативными библиотеками
React.useLayoutEffect(() => {
library.init(domNode);
}, []);
Важные моменты
1. Производительность
// ❌ ПЛОХО
// useLayoutEffect блокирует браузер
React.useLayoutEffect(() => {
for (let i = 0; i < 1000000000; i++) {
// Долгие вычисления
}
}, []);
// ✅ ХОРОШО
// useEffect не блокирует отрисовку
React.useEffect(() => {
for (let i = 0; i < 1000000000; i++) {
// Долгие вычисления
}
}, []);
2. Server-Side Rendering
// useLayoutEffect НЕ срабатывает на сервере
// useEffect тоже НЕ срабатывает на сервере (по умолчанию)
// Но это поведение зависит от фреймворка
3. Cleanup функции
Обе функции поддерживают cleanup функции:
React.useLayoutEffect(() => {
const subscription = subscribe();
return () => {
// Cleanup срабатывает ДО нового эффекта
subscription.unsubscribe();
};
}, [dependency]);
Резюме
- useLayoutEffect — синхронно, ДО отрисовки, блокирует браузер
- useEffect — асинхронно, ПОСЛЕ отрисовки, не блокирует браузер
- В 99% случаев используй useEffect
- Используй useLayoutEffect только если нужно измерить/изменить DOM перед отрисовкой