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

Приведи пример использования useLayoutEffect

2.0 Middle🔥 131 комментариев
#JavaScript Core

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

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

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

Разница между useEffect и useLayoutEffect

Перед примером важно понять ключевую разницу между useEffect и useLayoutEffect. Оба хука имеют одинаковуюсигнатуру (принимают функцию и массив зависимостей), но выполняются в разные фазы жизненного цикла React-компонента:

  • useEffect запускается после того, как браузер отрисовал изменения на экране и пользователь их видит. Это асинхронная операция, не блокирующая отрисовку.
  • useLayoutEffect запускается после вычисления изменений в виртуальном DOM, но до того, как браузер фактически отрисует (paint) эти изменения на экране. Это синхронная операция, которая блокирует отрисовку до своего завершения.

Важно: Поскольку useLayoutEffect блокирует визуальное обновление, его следует использовать только в случаях, когда измерения DOM или мутации должны быть синхронизированы с отрисовкой, чтобы избежать визуального "мерцания" или скачков.

Пример: Автоматическая синхронизация высоты текстового поля (Textarea Autosize)

Классический случай, когда useLayoutEffect предпочтительнее useEffect — это измерение и мутация DOM-элемента, размер которого зависит от контента. Использование useEffect может привести к тому, что пользователь сначала увидит элемент с одной высотой, а затем мгновенно — с другой ("мерцание").

Реализация компонента с useLayoutEffect:

import React, { useState, useLayoutEffect, useRef } from 'react';

const AutoResizingTextarea = () => {
  const [value, setValue] = useState('');
  const textareaRef = useRef(null);

  // Хук useLayoutEffect гарантирует, что мы изменим высоту ДО того,
  // как пользователь увидит результат отрисовки.
  useLayoutEffect(() => {
    const textarea = textareaRef.current;
    if (textarea) {
      // 1. Сбрасываем высоту на 'auto' для корректного расчета scrollHeight.
      textarea.style.height = 'auto';
      
      // 2. Измеряем необходимую высоту (scrollHeight включает весь контент с учетом padding).
      const newHeight = textarea.scrollHeight;
      
      // 3. Применяем новую высоту. Эта мутация будет отрисована вместе с остальным новым контентом.
      textarea.style.height = `${newHeight}px`;
    }
  }, [value]); // Эффект пересчитывает высоту при каждом изменении `value`.

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  // Инициализируем элемент с минимальной высотой 40px, которая будет меняться.
  return (
    <textarea
      ref={textareaRef}
      value={value}
      onChange={handleChange}
      placeholder="Введите текст..."
      style={{
        minHeight: '40px',
        width: '100%',
        boxSizing: 'border-box', // Важно для корректного расчета размеров.
        resize: 'none', // Отключаем стандартный ресайз пользователем.
        overflow: 'hidden', // Скрываем скроллбар, так как высота = контенту.
      }}
    />
  );
};

export default AutoResizingTextarea;

Почему в данном примере useLayoutEffect критически важен?

  1. Избегание визуального мерцания:
    *   Если использовать `useEffect`, последовательность будет такой:
        1.  Пользователь вводит символ.
        2.  React обновляет состояние `value` и виртуальный DOM.
        3.  Браузер **отрисовывает** `<textarea>` со **старой высотой** (например, 60px).
        4.  **Пользователь видит эту картину.**
        5.  Выполняется асинхронный `useEffect`.
        6.  Код в нем измеряет `scrollHeight` (например, 80px) и меняет `style.height`.
        7.  Браузер **перерисовывает** `<textarea>` с новой высотой 80px.
        *   Пользователь заметит скачок высоты между шагами 4 и 7.
    *   С `useLayoutEffect`:
        1.  Пользователь вводит символ.
        2.  React обновляет состояние `value` и виртуальный DOM.
        3.  Выполняется **синхронный `useLayoutEffect`**. DOM-элемент уже доступен, но браузер еще **не произвел отрисовку**.
        4.  Хук измеряет `scrollHeight` и **немедленно** задает новую высоту.
        5.  Браузер **одномоментно отрисовывает** финальное состояние `<textarea>` с уже обновленным контентом (`value`) и **скорректированной высотой**.
        *   Пользователь видит только конечный результат, без промежуточного состояния.

  1. Синхронные измерения DOM: Для вычисления scrollHeight нам нужны актуальные размеры DOM-элемента с новым контентом. useLayoutEffect гарантирует, что вычисления происходят после применения обновлений из React (в коммит-фазе), но до того как что-либо ушло в пиксели.

Ключевые сценарии использования useLayoutEffect

  • Измерение элементов DOM (получение offsetWidth, scrollHeight, getBoundingClientRect и т.д.) перед отрисовкой.
  • Мутирование DOM (изменение стилей, позиционирования) на основе только что выполненных измерений.
  • Предотвращение "разрыва" контента, когда его внешний вид зависит от начальной позиции или размеров (например, реализация тултипов, выпадающих меню, слайдеров, где нужно рассчитать позицию).

Правило от React

Команда React рекомендует: Сначала попробуйте useEffect. Используйте useLayoutEffect только тогда, когда визуальное "моргание" или несоответствие, вызванное useEffect, является проблемой для пользовательского опыта. В подавляющем большинстве случаев для запросов данных, подписок или ручных изменений DOM, которые могут быть отложены, достаточно useEffect.