Приведи пример использования useLayoutEffect
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между 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 критически важен?
- Избегание визуального мерцания:
* Если использовать `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`) и **скорректированной высотой**.
* Пользователь видит только конечный результат, без промежуточного состояния.
- Синхронные измерения DOM: Для вычисления
scrollHeightнам нужны актуальные размеры DOM-элемента с новым контентом.useLayoutEffectгарантирует, что вычисления происходят после применения обновлений из React (в коммит-фазе), но до того как что-либо ушло в пиксели.
Ключевые сценарии использования useLayoutEffect
- Измерение элементов DOM (получение
offsetWidth,scrollHeight,getBoundingClientRectи т.д.) перед отрисовкой. - Мутирование DOM (изменение стилей, позиционирования) на основе только что выполненных измерений.
- Предотвращение "разрыва" контента, когда его внешний вид зависит от начальной позиции или размеров (например, реализация тултипов, выпадающих меню, слайдеров, где нужно рассчитать позицию).
Правило от React
Команда React рекомендует: Сначала попробуйте useEffect. Используйте useLayoutEffect только тогда, когда визуальное "моргание" или несоответствие, вызванное useEffect, является проблемой для пользовательского опыта. В подавляющем большинстве случаев для запросов данных, подписок или ручных изменений DOM, которые могут быть отложены, достаточно useEffect.