useLayoutEffect¶
Производительность
useLayoutEffect
может снизить производительность. Предпочитайте useEffect
, когда это возможно.
useLayoutEffect
- это версия useEffect
, которая срабатывает перед тем, как браузер перерисовывает экран.
1 |
|
Описание¶
useLayoutEffect(setup, dependencies?)
¶
Вызывает useLayoutEffect
для выполнения измерений макета перед тем, как браузер перерисовывает экран:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Параметры¶
setup
: Функция с логикой вашего Эффекта. Ваша функция настройки может также возвращать функцию cleanup. Прежде чем ваш компонент будет добавлен в DOM, React запустит вашу функцию настройки. После каждого повторного рендеринга с измененными зависимостями React будет сначала запускать функцию очистки (если вы ее предоставили) со старыми значениями, а затем запускать вашу функцию настройки с новыми значениями. Прежде чем ваш компонент будет удален из DOM, React запустит вашу функцию очистки.- опционально
dependencies
: Список всех реактивных значений, на которые ссылается кодsetup
. Реактивные значения включаютprops
,state
, а также все переменные и функции, объявленные непосредственно в теле вашего компонента. Если ваш линтер настроен на React, он проверит, что каждое реактивное значение правильно указано в качестве зависимости. Список зависимостей должен иметь постоянное количество элементов и быть написан inline по типу[dep1, dep2, dep3]
. React будет сравнивать каждую зависимость с предыдущим значением, используя сравнениеObject.is
. Если вы опустите этот аргумент, ваш Effect будет запускаться заново после каждого повторного рендеринга компонента.
Возвращает¶
useLayoutEffect
возвращает undefined
.
Ограничения¶
useLayoutEffect
является хуком, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или ваших собственных хуков. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, извлеките компонент и переместите эффект туда.- Когда включен строгий режим, React будет запускать один дополнительный цикл настройки+очистки перед первой реальной настройкой. Это стресс-тест, который гарантирует, что ваша логика очистки "отражает" вашу логику настройки и что она останавливает или отменяет все, что делает настройка. Если это вызывает проблему, реализуйте функцию очистки.
- Если некоторые из ваших зависимостей являются объектами или функциями, определенными внутри компонента, есть риск, что они приведут к тому, что Эффект будет перезапускаться чаще, чем нужно. Чтобы исправить это, удалите ненужные зависимости
object
иfunction
. Вы также можете извлекать обновления состояния и нереактивную логику наружу вашего Эффекта. - Эффекты работают только на клиенте. Они не работают во время рендеринга на сервере.
- Код внутри
useLayoutEffect
и все запланированные обновления состояния блокируют браузер от перерисовки экрана. При чрезмерном использовании это делает ваше приложение медленным. Когда это возможно, предпочитайтеuseEffect
.
Использование¶
Измерение макета до того, как браузер перерисует экран¶
Большинству компонентов не нужно знать свое положение и размер на экране, чтобы решить, что отображать. Они только возвращают некоторый JSX. Затем браузер вычисляет их разметку (положение и размер) и перерисовывает экран.
Иногда этого недостаточно. Представьте себе всплывающую подсказку, которая появляется рядом с каким-то элементом при наведении. Если места достаточно, всплывающая подсказка должна появиться над элементом, но если она не помещается, то должна появиться под ним. Чтобы отобразить всплывающую подсказку в правильном конечном положении, необходимо знать ее высоту (т.е. поместится ли она сверху).
Для этого необходимо выполнить рендеринг в два прохода:
- Отрисовать всплывающую подсказку в любом месте (даже при неправильном положении).
- Измерьте его высоту и решите, где разместить всплывающую подсказку.
- Снова отобразите всплывающую подсказку в правильном месте.
Все это должно произойти до того, как браузер перерисует экран. Вы не хотите, чтобы пользователь видел, что всплывающая подсказка перемещается. Вызовите useLayoutEffect
, чтобы выполнить измерения макета до того, как браузер перерисует экран:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Вот как это работает шаг за шагом:
Tooltip
рендерится с начальнымtooltipHeight = 0
(поэтому всплывающая подсказка может быть неправильно позиционирована).- React помещает ее в DOM и запускает код в
useLayoutEffect
. - ержимого всплывающей подсказки и вызывает немедленный повторный рендеринг.
Tooltip
рендерится снова с реальнымtooltipHeight
(чтобы всплывающая подсказка была правильно расположена).- React обновляет его в DOM, и браузер наконец отображает всплывающую подсказку.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Обратите внимание, что хотя компонент Tooltip
должен быть отрисован в два прохода (сначала с tooltipHeight
, инициализированным в 0
, а затем с реальной измеренной высотой), вы видите только конечный результат. Вот почему для этого примера вам нужен useLayoutEffect
вместо useEffect
. Давайте рассмотрим разницу в деталях ниже.
useLayoutEffect vs useEffect¶
1. useLayoutEffect
блокирует браузер от перерисовки
React гарантирует, что код внутри useLayoutEffect
и любые обновления состояния, запланированные внутри него, будут обработаны до того, как браузер перерисует экран. Это позволяет вам отрисовать всплывающую подсказку, измерить ее, и снова отрисовать ее так, чтобы пользователь не заметил первого дополнительного отрисовки. Другими словами, useLayoutEffect
блокирует браузер от рисования.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
2. useEffect
не блокирует браузер
Вот тот же пример, но с useEffect
вместо useLayoutEffect
. Если у вас медленное устройство, вы можете заметить, что иногда всплывающая подсказка "мерцает", и вы на короткое время видите ее начальное положение перед исправленным положением.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Чтобы ошибку было легче воспроизвести, в этой версии добавлена искусственная задержка во время рендеринга. React позволит браузеру закрасить экран до того, как обработает обновление состояния внутри useEffect
. В результате всплывающая подсказка мерцает:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Измените этот пример на useLayoutEffect
и обратите внимание, что он блокирует закраску, даже если рендеринг замедлен.
Производительность
Рендеринг в два прохода и блокировка браузера снижают производительность. Старайтесь избегать этого, когда можете.
Устранение неполадок¶
Я получаю ошибку: "useLayoutEffect
ничего не делает на сервере"¶
Цель useLayoutEffect
- позволить вашему компоненту использовать информацию о макете для рендеринга:
- Рендеринг начального содержимого.
- Измерить макет до того, как браузер перерисует экран.
- Отрисуйте конечный контент, используя информацию о макете, которую вы считали.
Когда вы или ваш фреймворк использует серверный рендеринг, ваше приложение React рендерит в HTML на сервере для первоначального рендеринга. Это позволяет вам показать начальный HTML до загрузки кода JavaScript.
Проблема в том, что на сервере нет информации о макете.
В предыдущем примере вызов useLayoutEffect
в компоненте Tooltip
позволяет ему правильно позиционироваться (либо выше, либо ниже содержимого) в зависимости от высоты содержимого. Если бы вы попытались отобразить Tooltip
как часть исходного серверного HTML, это было бы невозможно определить. На сервере еще нет макета! Поэтому, даже если бы вы отобразили его на сервере, его положение "перепрыгнуло" бы на клиенте после загрузки и выполнения JavaScript.
Обычно компоненты, которые полагаются на информацию о макете, не нуждаются в рендеринге на сервере. Например, вероятно, нет смысла показывать Tooltip
во время первоначального рендеринга. Она вызывается взаимодействием с клиентом.
Однако, если вы столкнулись с этой проблемой, у вас есть несколько вариантов:
- Замените
useLayoutEffect
наuseEffect
. Это говорит React, что можно отображать начальный результат рендеринга без блокировки закраски (потому что исходный HTML станет виден до запуска вашего Эффекта). - В качестве альтернативы, пометьте свой компонент как предназначенный только для клиентов Это говорит React заменить содержимое до ближайшей границы
<Suspense>
на фаллбэк загрузки (например, спиннер или мерцание) во время серверного рендеринга. - Альтернативно, вы можете рендерить компонент с
useLayoutEffect
только после гидратации. Храните состояние booleanisMounted
, которое инициализируется вfalse
, и устанавливайте его вtrue
внутри вызоваuseEffect
. Ваша логика рендеринга может быть такой:return isMounted ? <RealContent /> : <FallbackContent />
. На сервере и во время гидратации пользователь увидитFallbackContent
, который не должен вызыватьuseLayoutEffect
. Затем React заменит его наRealContent
, который работает только на клиенте и может включать вызовыuseLayoutEffect
. - Если вы синхронизируете свой компонент с внешним хранилищем данных и полагаетесь на
useLayoutEffect
по причинам, отличным от измерения макета, рассмотрите вместо негоuseSyncExternalStore
, который поддерживает серверный рендеринг.
Источник — https://react.dev/reference/react/useLayoutEffect