← Назад к вопросам

Что срабатывает раньше useEffect или useLayoutEffect?

1.8 Middle🔥 171 комментариев
#React#Оптимизация и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# useEffect vs useLayoutEffect: Порядок выполнения

Это один из самых частых вопросов на собеседованиях и очень важно понимать разницу между этими двумя хуками. Порядок выполнения критически влияет на производительность и поведение приложения.

Краткий ответ

useLayoutEffect срабатывает РАНЬШЕ, чем useEffect.

Порядок:

  1. React обновляет DOM
  2. useLayoutEffect срабатывает (синхронно)
  3. Браузер рисует (отображает изменения)
  4. 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 перед отрисовкой