useEffect¶
useEffect
- это хук React, который позволяет вам синхронизировать компонент с внешней системой.
1 |
|
Описание¶
useEffect(setup, dependencies?)
¶
Вызовите useEffect
на верхнем уровне вашего компонента, чтобы объявить Эффект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Параметры¶
setup
: Функция с логикой вашего Эффекта. Ваша функция настройки может также по желанию возвращать функцию cleanup. Когда ваш компонент будет добавлен в DOM, React запустит вашу функцию настройки. После каждого повторного рендеринга с измененными зависимостями React будет сначала запускать функцию очистки (если вы ее предоставили) со старыми значениями, а затем запускать вашу функцию настройки с новыми значениями. После удаления вашего компонента из DOM React запустит вашу функцию очистки.- опциональные
dependencies
: Список всех реактивных значений, на которые ссылается кодsetup
. Реактивные значения включают props, state, а также все переменные и функции, объявленные непосредственно в теле вашего компонента. Если ваш линтер настроен на React, он проверит, что каждое реактивное значение правильно указано в качестве зависимости. Список зависимостей должен иметь постоянное количество элементов и быть написан inline по типу[dep1, dep2, dep3]
. React будет сравнивать каждую зависимость с предыдущим значением, используя сравнениеObject.is
. Если вы опустите этот аргумент, ваш Effect будет запускаться заново после каждого повторного рендеринга компонента.
Возврат¶
useEffect
возвращает undefined
.
Ограничения¶
-
useEffect
- это хук, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или ваших собственных хуков. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, создайте новый компонент и переместите состояние в него. -
Если вы не пытаетесь синхронизироваться с какой-то внешней системой, вам, вероятно, не нужен Эффект.
-
Когда включен строгий режим, React будет проводить один дополнительный цикл настройки+очистки только для разработки перед первой реальной настройкой. Это стресс-тест, который гарантирует, что ваша логика очистки "отражает" вашу логику настройки и что она останавливает или отменяет все, что делает настройка. Если это вызывает проблему, реализуйте функцию очистки.
-
Если некоторые из ваших зависимостей являются объектами или функциями, определенными внутри компонента, есть риск, что они приведут к тому, что Эффект будет перезапускаться чаще, чем нужно. Чтобы исправить это, удалите ненужные зависимости
object
иfunction
. Вы также можете извлекать обновления состояния и нереактивную логику вне вашего Эффекта. -
Если ваш Эффект не был вызван взаимодействием (например, щелчком мыши), React позволит браузеру сначала нарисовать обновленный экран, прежде чем запустить ваш Эффект. Если ваш Эффект делает что-то визуальное (например, позиционирует всплывающую подсказку), и задержка заметна (например, она мерцает), замените
useEffect
наuseLayoutEffect
. -
Даже если ваш Эффект был вызван взаимодействием (например, щелчком), браузер может перерисовать экран до обработки обновлений состояния внутри вашего Эффекта. Обычно это то, что вам нужно. Однако, если вы должны запретить браузеру перерисовывать экран, вам нужно заменить
useEffect
наuseLayoutEffect
. -
Эффекты работают только на клиенте. Они не работают во время серверного рендеринга.
Использование¶
Подключение к внешней системе¶
Некоторые компоненты должны оставаться подключенными к сети, API браузера или сторонней библиотеке, пока они отображаются на странице. Эти системы не контролируются React, поэтому их называют внешними.
Чтобы подключить ваш компонент к какой-либо внешней системе, вызовите useEffect
на верхнем уровне вашего компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Вам необходимо передать два аргумента в useEffect
:
- Функция setup с установочным кодом, которая подключается к этой системе.
- Она должна возвращать функцию очистки с кодом очистки, которая отсоединяется от этой системы.
- Список зависимостей, включающий каждое значение из вашего компонента, используемое внутри этих функций.
React вызывает ваши функции установки и очистки всякий раз, когда это необходимо, что может происходить несколько раз:.
- Ваш код установки выполняется, когда ваш компонент добавляется на страницу (mounts).
- После каждого повторного рендеринга вашего компонента, когда зависимости изменились:
- Сначала ваш очищающий код запускается со старыми пропсами и состоянием.
- Затем, ваш установочный код запускается с новыми пропсами и состоянием.
- Ваш очищающий код запускается в последний раз после того, как ваш компонент удаляется со страницы (размонтируется).
Давайте проиллюстрируем эту последовательность на примере выше.
Когда вышеуказанный компонент ChatRoom
добавляется на страницу, он подключается к чату с начальными serverUrl
и roomId
. Если serverUrl
или roomId
изменятся в результате повторного рендеринга (например, если пользователь выберет другую комнату в выпадающем списке), ваш Эффект отключится от предыдущей комнаты и подключится к следующей. Когда компонент ChatRoom
будет удален со страницы, ваш Эффект отключится в последний раз.
Чтобы помочь вам найти ошибки, при разработке React запускает setup и cleanup один дополнительный раз перед setup. Это стресс-тест, который проверяет правильность реализации логики вашего Эффекта. Если это вызывает видимые проблемы, значит, вашей функции очистки не хватает логики. Функция очистки должна остановить или отменить все, что делала функция настройки. Эмпирическое правило гласит, что пользователь не должен различать между однократным вызовом функции setup (как в продакшене) и последовательностью setup → cleanup → setup (как в разработке). См. общие решения.
Попробуйте писать каждый эффект как независимый процесс и думать об одном цикле установки/очистки за раз. Не должно иметь значения, монтируется, обновляется или размонтируется ваш компонент. Когда ваша логика очистки правильно "зеркалит" логику установки, ваш Эффект устойчив к запуску установки и очистки так часто, как это необходимо.
Подключение к внешней системе
Эффект позволяет синхронизировать ваш компонент с какой-либо внешней системой (например, с чат-службой). Здесь под внешней системой подразумевается любой кусок кода, не контролируемый React, например:
- Таймер, управляемый с помощью
setInterval()
иclearInterval()
. - Подписка на события с помощью
window.addEventListener()
иwindow.removeEventListener()
. - Сторонняя библиотека анимации с API типа
animation.start()
иanimation.reset()
.
Если вы не подключаетесь к какой-либо внешней системе, вам, вероятно, не нужен эффект.
Примеры подключения к внешней системе¶
1. Подключение к чат-серверу
В этом примере компонент ChatRoom
использует эффект, чтобы оставаться подключенным к внешней системе, определенной в chat.js
. Нажмите "Открыть чат", чтобы появился компонент ChatRoom
. Эта песочница работает в режиме разработки, поэтому существует дополнительный цикл подключения и отключения, как объясняется здесь Попробуйте изменить roomId
и erverUrl
с помощью выпадающего списка и ввода, и посмотрите, как Эффект снова подключается к чату. Нажмите "Закрыть чат", чтобы увидеть, как Эффект отключится в последний раз.
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
2. Прослушивание глобального события браузера
В этом примере внешней системой является сам DOM браузера. Обычно вы указываете слушателей событий с помощью JSX, но вы не можете прослушивать глобальный объект window
таким образом. Эффект позволяет вам подключиться к объекту window
и прослушивать его события. Прослушивание события pointermove
позволяет отслеживать положение курсора (или пальца) и обновлять красную точку, чтобы она двигалась вместе с ним.
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 |
|
3. Запуск анимации
В этом примере внешней системой является библиотека анимации в animation.js
. Она предоставляет класс JavaScript под названием FadeInAnimation
, который принимает узел DOM в качестве аргумента и раскрывает методы start()
и stop()
для управления анимацией. Этот компонент использует ссылку для доступа к базовому узлу DOM. Эффект считывает узел 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 42 43 44 |
|
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 |
|
4. Управление модальным диалогом
В этом примере внешней системой является DOM браузера. Компонент ModalDialog
отображает элемент <dialog>
. Он использует Effect для синхронизации свойства isOpen
с вызовами методов showModal()
и close()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
5. Отслеживание видимости элемента
В этом примере внешней системой снова является DOM браузера. Компонент App
отображает длинный список, затем компонент Box
, а затем еще один длинный список. Прокрутите список вниз. Обратите внимание, что когда компонент Box
появляется в области просмотра, цвет фона меняется на черный. Чтобы реализовать это, компонент Box
использует Effect для управления IntersectionObserver
. Этот API браузера уведомляет вас, когда элемент DOM становится видимым в области просмотра.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
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 |
|
Обертывание эффектов в пользовательские хуки¶
Эффекты - это "аварийный люк": Вы используете их, когда вам нужно "выйти за пределы React" и когда нет лучшего встроенного решения для вашего случая использования. Если вы часто сталкиваетесь с необходимостью вручную писать Effects, это обычно признак того, что вам нужно извлечь некоторые пользовательские хуки для общего поведения, на которое полагаются ваши компоненты.
Например, этот пользовательский хук useChatRoom
"прячет" логику вашего Эффекта за более декларативным API:
1 2 3 4 5 6 7 8 9 10 11 |
|
Затем вы можете использовать его из любого компонента, как это сделано здесь:
1 2 3 4 5 6 7 8 9 10 11 |
|
В экосистеме React также существует множество отличных пользовательских хуков для любых целей.
Подробнее об обертывании эффектов в пользовательские хуки
Примеры обертывания эффектов в пользовательские хуки¶
1. Пользовательский хук useChatRoom
Этот пример идентичен одному из предыдущих примеров, но логика вынесена в пользовательский хук.
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 44 45 46 47 48 49 50 51 52 53 54 55 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
2. Пользовательский хук useWindowListener
Этот пример идентичен одному из предыдущих примеров, но логика вынесена в пользовательский хук.
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 |
|
1 2 3 4 5 6 7 8 9 10 |
|
3. Пользовательский хук useIntersectionObserver
Этот пример идентичен одному из предыдущих примеров, но логика частично вынесена в пользовательский хук.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
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 |
|
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 |
|
Управление виджетом без React¶
Иногда вы хотите, чтобы внешняя система синхронизировалась с каким-либо параметром или состоянием вашего компонента.
Например, если у вас есть сторонний виджет карты или компонент видеоплеера, написанный без React, вы можете использовать эффект для вызова методов, которые заставят его состояние соответствовать текущему состоянию вашего компонента React. Этот Эффект создает экземпляр класса MapWidget
, определенного в map-widget.js
. Когда вы изменяете параметр zoomLevel
компонента Map
, Эффект вызывает setZoom()
для экземпляра класса, чтобы сохранить его синхронизацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
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 |
|
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 |
|
В данном примере функция очистки не нужна, поскольку класс MapWidget
управляет только узлом DOM, который был ему передан. После удаления React-компонента Map
из дерева, и DOM-узел, и экземпляр класса MapWidget
будут автоматически очищены от мусора JavaScript-движком браузера.
Получение данных с помощью эффектов¶
Вы можете использовать Эффект для получения данных для вашего компонента. Обратите внимание, что если вы используете фреймворк, использование механизма получения данных вашего фреймворка будет намного эффективнее, чем написание эффектов вручную.
Если вы хотите получить данные из Эффекта вручную, ваш код может выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Обратите внимание на переменную ignore
, которая инициализируется в false
и устанавливается в true
во время очистки. Это гарантирует, что ваш код не пострадает от "условий гонки": ответы сети могут приходить не в том порядке, в котором вы их отправили.
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 |
|
Вы также можете переписать, используя синтаксис async
/ await
, но вам все равно придется предоставить функцию очистки:
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 |
|
Запись получения данных непосредственно в Effects становится повторяющейся и затрудняет добавление таких оптимизаций, как кэширование и серверный рендеринг. Проще использовать пользовательский хук - либо свой собственный, либо поддерживаемый сообществом.
Какие есть хорошие альтернативы для получения данных в Effects?
Написание вызовов fetch
внутри Effects является популярным способом получения данных, особенно в полностью клиентских приложениях. Однако это очень ручной подход, и у него есть существенные недостатки:
- Эффекты не запускаются на сервере. Это означает, что первоначальный HTML, отрисованный на сервере, будет содержать только состояние загрузки без каких-либо данных. Клиентский компьютер должен будет загрузить весь JavaScript и отобразить ваше приложение только для того, чтобы обнаружить, что теперь ему нужно загрузить данные. Это не очень эффективно.
- Получение данных непосредственно в Effects позволяет легко создавать "сетевые водопады". Вы рендерите родительский компонент, он получает некоторые данные, рендерит дочерние компоненты, а затем они начинают получать свои данные. Если сеть не очень быстрая, это значительно медленнее, чем параллельная выборка всех данных.
- Например, если компонент размонтируется, а затем снова монтируется, ему придется снова получать данные.
- Это не очень эргономично. Существует довольно много кода, связанного с написанием вызовов
fetch
таким образом, чтобы не страдать от ошибок типа race conditions.
Этот список недостатков не является специфическим для React. Он применим к выборке данных при подключении с помощью любой библиотеки. Как и в случае с маршрутизацией, выборка данных не является тривиальной задачей, поэтому мы рекомендуем следующие подходы:
- Если вы используете фреймворк, используйте его встроенный механизм выборки данных. Современные фреймворки React имеют встроенные механизмы выборки данных, которые эффективны и не страдают от описанных выше подводных камней.
- В противном случае, рассмотрите возможность использования или создания кэша на стороне клиента. Популярные решения с открытым исходным кодом включают React Query, useSWR и React Router 6.4+. Вы также можете создать собственное решение, в этом случае вы будете использовать Effects под капотом, но также добавите логику для дедупликации запросов, кэширования ответов и избежания сетевых водопадов (путем предварительной загрузки данных или поднятия требований к данным в маршрутах).
Вы можете продолжать получать данные непосредственно в Effects, если ни один из этих подходов вам не подходит.
Указание реактивных зависимостей¶
Обратите внимание, что вы не можете "выбрать" зависимости вашего Эффекта. Каждый реактивное значение, используемое вашим Эффектом, может быть использован в качестве зависимого.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Если serverUrl
или roomId
изменятся, ваш Эффект переподключится к чату, используя новые значения.
Реактивные значения включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку roomId
и erverUrl
являются реактивными значениями, вы не можете удалить их из зависимостей. Если вы попытаетесь опустить их и ваш линтер правильно настроен для React, линтер отметит это как ошибку, которую нужно исправить:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Чтобы удалить зависимость, вам нужно "доказать" линтеру, что она не должна быть зависимостью. Например, вы можете убрать serverUrl
из вашего компонента, чтобы доказать, что он не реактивный и не будет меняться при повторных рендерах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Теперь, когда serverUrl
не является реактивным значением (и не может меняться при повторном рендере), ему не нужно быть зависимостью. Если код вашего Эффекта не использует никаких реактивных значений, его список зависимостей должен быть пустым ([]
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Эффект с пустыми зависимостями не перезапускается при изменении пропсов или состояния вашего компонента.
Подавление линтера
Если у вас есть существующая кодовая база, у вас могут быть некоторые Эффекты, которые подавляют линтер подобным образом:
1 2 3 4 5 |
|
Когда зависимости не соответствуют коду, существует высокий риск появления ошибок. Подавляя линтер, вы "лжете" React о значениях, от которых зависит ваш Эффект. Вместо этого докажите, что они не нужны.
Примеры передачи реактивных зависимостей¶
1. Передача массива зависимостей
Если вы укажете зависимости, ваш Эффект запустится после первоначального рендера и после повторных рендеров с измененными зависимостями.
1 2 3 |
|
В приведенном ниже примере serverUrl
и roomId
являются реактивными значениями, поэтому они оба должны быть указаны как зависимости. В результате выбор другой комнаты в выпадающем списке или редактирование URL сервера приводит к повторному подключению чата. Однако, поскольку message
не используется в эффекте (и поэтому не является зависимостью), редактирование сообщения не приводит к повторному подключению к чату.
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
2. Передача пустого массива зависимостей
Если ваш Эффект действительно не использует никаких реактивных значений, он будет запущен только после начального рендеринга.
1 2 3 |
|
Даже при пустых зависимостях, setup и cleanup будут выполняться один дополнительный раз в разработке, чтобы помочь вам найти ошибки.
В этом примере и serverUrl
и roomId
жестко закодированы. Поскольку они объявлены вне компонента, они не являются реактивными значениями, а значит, не являются зависимостями. Список зависимостей пуст, поэтому Effect не запускается при повторном рендере.
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 44 45 46 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
3. Непередача массива зависимостей вообще
Если вы не передаете массив зависимостей вообще, ваш Effect запускается после каждого рендера (и повторного рендера) вашего компонента.
1 2 3 |
|
В этом примере Effect повторно запускается при изменении serverUrl
и roomId
, что вполне разумно. Однако он также запускается повторно, когда вы изменяете message
, что, вероятно, нежелательно. Вот почему обычно указывается массив зависимостей.
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Обновление состояния на основе предыдущего состояния от Эффекта¶
Когда вы хотите обновить состояние на основе предыдущего состояния Эффекта, вы можете столкнуться с проблемой:
1 2 3 4 5 6 7 8 9 10 11 |
|
Поскольку count
является реактивным значением, оно должно быть указано в списке зависимостей. Однако это заставляет Effect очищать и устанавливать заново каждый раз, когда изменяется count
. Это не идеально.
Чтобы исправить это, передайте функцию обновления состояния c => c + 1
в setCount
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Теперь, когда вы передаете c => c + 1
вместо count + 1
, вашему Эффекту больше не нужно зависеть от count
. В результате этого исправления, ему не нужно будет очищать и устанавливать интервал заново каждый раз, когда count
меняется.
Удаление ненужных объектных зависимостей¶
Если ваш Эффект зависит от объекта или функции, созданной во время рендеринга, он может запускаться слишком часто. Например, этот Эффект переподключается после каждого рендеринга, потому что объект options
разный для каждого рендеринга:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Избегайте использования объекта, созданного во время рендеринга, в качестве зависимого. Вместо этого создайте объект внутри Effect:
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 44 45 46 47 48 49 50 51 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Теперь, когда вы создаете объект options
внутри эффекта, сам эффект зависит только от строки roomId
.
С этим исправлением ввод ввода не переподключает чат. В отличие от объекта, который создается заново, строка типа roomId
не меняется, пока вы не зададите ей другое значение. Подробнее об удалении зависимостей .
Удаление ненужных зависимостей от функций¶
Если ваш Эффект зависит от объекта или функции, созданной во время рендеринга, он может запускаться слишком часто. Например, этот Эффект переподключается после каждого рендера, потому что функция createOptions
разная для каждого рендера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Само по себе создание функции с нуля при каждом повторном рендере не является проблемой. Вам не нужно ее оптимизировать. Однако если вы используете ее в качестве зависимости от вашего Эффекта, это приведет к тому, что ваш Эффект будет запускаться заново после каждого повторного рендеринга.
Избегайте использования функции, созданной во время рендеринга, в качестве зависимости. Вместо этого объявите ее внутри Эффекта:
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 44 45 46 47 48 49 50 51 52 53 54 55 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Теперь, когда вы определяете функцию createOptions
внутри эффекта, сам эффект зависит только от строки roomId
. С этим исправлением ввод в input не переподключает чат. В отличие от функции, которая создается заново, такая строка, как roomId
, не меняется, пока вы не установите для нее другое значение. Подробнее об удалении зависимостей .
Чтение последних пропсов и состояния из эффекта¶
Экспериментальный API
Этот раздел описывает экспериментальный API, который еще не был выпущен в стабильной версии React.
По умолчанию, когда вы читаете реактивное значение из Эффекта, вы должны добавить его как зависимость. Это гарантирует, что ваш Эффект "реагирует" на каждое изменение этого значения. Для большинства зависимостей это именно то поведение, которое вам нужно.
Однако иногда вы хотите читать последние пропсы и состояния из Эффекта без "реакции" на них. Например, представьте, что вы хотите регистрировать количество товаров в корзине при каждом посещении страницы:
1 2 3 4 5 6 |
|
Что если вы хотите регистрировать посещение новой страницы после каждого изменения url
, но не, если меняется только shoppingCart
? Вы не можете исключить shoppingCart
из зависимостей, не нарушая правила реактивности. Однако вы можете выразить, что вы не хотите, чтобы часть кода "реагировала" на изменения, даже если она вызывается из Эффекта. Объявите событие Эффекта с помощью хука useEffectEvent
и переместите код, читающий shoppingCart
, внутрь него:
1 2 3 4 5 6 7 8 9 10 |
|
События Эффекта не являются реактивными и всегда должны быть исключены из зависимостей вашего Эффекта. Именно это позволяет вам поместить нереактивный код (где вы можете прочитать последнее значение некоторых пропсов и состояния) внутрь них. Читая shoppingCart
внутри onVisit
, вы гарантируете, что shoppingCart
не будет повторно запускать ваш Эффект.
Подробнее о том, как события Effect Events позволяют разделить реактивный и нереактивный код .
Отображение разного содержимого на сервере и клиенте¶
Если ваше приложение использует серверный рендеринг (либо напрямую, либо через фреймворк), ваш компонент будет отображаться в двух разных средах. На сервере он будет рендериться для создания исходного HTML. На клиенте React снова запустит код рендеринга, чтобы прикрепить обработчики событий к этому HTML. Вот почему, чтобы hydration работал, ваш первоначальный вывод рендеринга должен быть идентичным на клиенте и на сервере.
В редких случаях вам может понадобиться отобразить разное содержимое на клиенте. Например, если ваше приложение считывает некоторые данные из localStorage
, оно не может сделать это на сервере. Вот как это можно реализовать:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Пока приложение загружается, пользователь будет видеть начальный вывод рендера. Затем, после загрузки и гидратации, ваш Effect запустится и установит didMount
в true
, вызывая повторный рендеринг. При этом произойдет переключение на вывод рендера только для клиента. Эффекты не запускаются на сервере, поэтому didMount
было false
во время первоначального серверного рендеринга.
Используйте этот паттерн экономно. Помните, что пользователи с медленным соединением будут видеть начальное содержимое довольно долго - потенциально много секунд - поэтому вы не захотите вносить резкие изменения во внешний вид вашего компонента. Во многих случаях вы можете избежать этого, условно показывая различные вещи с помощью CSS.
Устранение неполадок¶
Мой эффект запускается дважды, когда компонент монтируется¶
Когда включен строгий режим, в процессе разработки React запускает установку и очистку один дополнительный раз перед фактической установкой.
Это стресс-тест, который проверяет правильность реализации логики вашего Эффекта. Если это вызывает видимые проблемы, значит, вашей функции очистки не хватает логики. Функция очистки должна остановить или отменить все, что делала функция настройки. Эмпирическое правило гласит, что пользователь не должен различать между однократным вызовом функции setup (как в производстве) и последовательностью setup → cleanup → setup (как в разработке).
Подробнее о как это помогает найти ошибки и как исправить логику
Мой эффект запускается после каждого рендера¶
Во-первых, проверьте, не забыли ли вы указать массив зависимостей:
1 2 3 |
|
Если вы указали массив зависимостей, но ваш Effect все равно перезапускается в цикле, это связано с тем, что одна из ваших зависимостей отличается при каждом повторном рендере.
Вы можете отладить эту проблему, вручную записав журнал зависимостей в консоль:
1 2 3 4 5 |
|
Затем вы можете щелкнуть правой кнопкой мыши на массивах из разных рендеров в консоли и выбрать "Store as a global variable" для обоих. Предположив, что первый массив был сохранен как temp1
, а второй - как temp2
, вы можете использовать консоль браузера, чтобы проверить, является ли каждая зависимость в обоих массивах одинаковой:
1 2 3 4 5 6 7 8 |
|
Когда вы обнаружите зависимость, которая отличается при каждом повторном рендере, вы обычно можете исправить ее одним из этих способов:
- Обновление состояния на основе предыдущего состояния эффекта
- Удаление ненужных объектных зависимостей
- Удаление ненужных зависимостей функций
- Чтение последних пропсов и состояния из эффекта
В крайнем случае (если эти методы не помогли), оберните его создание с помощью useMemo
или useCallback
(для функций).
Мой Эффект повторяется в бесконечном цикле¶
Если ваш Эффект запускается в бесконечном цикле, эти две вещи должны быть верны:
- Ваш Эффект обновляет некоторое состояние.
- Это состояние приводит к повторному рендерингу, что вызывает изменение зависимостей Эффекта.
Прежде чем приступить к устранению проблемы, спросите себя, подключается ли ваш Эффект к какой-либо внешней системе (например, DOM, сеть, сторонний виджет и так далее). Зачем вашему Эффекту нужно устанавливать состояние? Синхронизируется ли он с этой внешней системой? Или вы пытаетесь управлять потоком данных вашего приложения вместе с ней?
Если внешней системы нет, подумайте, не упростит ли вашу логику полное удаление эффекта.
Если вы действительно синхронизируетесь с какой-то внешней системой, подумайте, почему и при каких условиях ваш Эффект должен обновлять состояние. Изменилось ли что-то, что влияет на визуальный вывод вашего компонента? Если вам нужно отслеживать какие-то данные, которые не используются при визуализации, то более подходящим вариантом может быть ref (который не вызывает повторного рендеринга). Убедитесь, что ваш эффект не обновляет состояние (и не запускает повторные рендеринги) чаще, чем это необходимо.
Наконец, если ваш Эффект обновляет состояние в нужное время, но цикл все еще существует, это связано с тем, что обновление состояния приводит к изменению одной из зависимостей Эффекта.
Моя логика очистки выполняется, даже если компонент не был размонтирован¶
Функция очистки запускается не только во время размонтирования, но и перед каждым повторным рендерингом с измененными зависимостями. Кроме того, в процессе разработки React запускает setup+cleanup один дополнительный раз сразу после монтирования компонента.
Если у вас есть код очистки без соответствующего кода установки, это обычно запах кода:
1 2 3 4 5 6 |
|
Ваша логика очистки должна быть "симметрична" логике настройки и должна останавливать или отменять все, что сделала настройка:
1 2 3 4 5 6 7 |
|
Узнайте, чем жизненный цикл эффекта отличается от жизненного цикла компонента .
Мой эффект делает что-то визуальное, и я вижу мерцание перед его запуском¶
Если ваш Эффект должен блокировать браузер от рисования экрана, замените useEffect
на useLayoutEffect
. Обратите внимание, что это не нужно для подавляющего большинства Эффектов. Это понадобится только в том случае, если очень важно запустить ваш Эффект до рисования браузера: например, для измерения и позиционирования всплывающей подсказки до того, как пользователь ее увидит.
Источник — https://react.dev/reference/react/useEffect