Удаление зависимостей эффектов¶
Когда вы пишете Эффект, линтер проверяет, включили ли вы все реактивные значения (такие как props и state), которые Эффект считывает, в список зависимостей вашего Эффекта. Это гарантирует, что ваш Эффект будет синхронизирован с последними пропсами и состоянием вашего компонента. Ненужные зависимости могут привести к тому, что ваш Эффект будет запускаться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших Эффектов.
Вы узнаете
- Как исправить бесконечные циклы зависимостей Эффектов
- Что делать, если вы хотите удалить зависимость
- Как считать значение из вашего Эффекта, не "реагируя" на него
- Как и почему следует избегать зависимостей от объектов и функций
- Почему подавление линтера зависимостей опасно, и что делать вместо этого
Зависимости должны соответствовать коду¶
Когда вы пишете эффект, вы сначала указываете, как запускать и останавливать то, что вы хотите, чтобы делал ваш эффект:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Затем, если вы оставите Effect dependencies пустым ([]
), линтер предложит правильные зависимости:
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 |
|
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 |
|
Эффекты "реагируют" на реактивные значения. Поскольку roomId
является реактивным значением (оно может измениться в результате повторного рендеринга), линтер проверяет, указали ли вы его в качестве зависимости. Если roomId
получит другое значение, React пересинхронизирует ваш 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 |
|
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 12 13 14 15 |
|
Реактивные значения включают props и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку roomId
является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер не позволит этого сделать:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
И линтер будет прав! Поскольку roomId
может меняться со временем, это внесет ошибку в ваш код.
Чтобы удалить зависимость, "докажите" линтеру, что она не должна быть зависимостью. Например, вы можете убрать roomId
из вашего компонента, чтобы доказать, что он не реактивный и не будет меняться при повторных рендерингах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Теперь, когда roomId
не является реактивным значением (и не может измениться при повторном рендере), ему не нужно быть зависимостью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Вот почему теперь вы можете указать пустой ([]
) список зависимостей Ваш эффект действительно больше не зависит ни от какого реактивного значения, поэтому ему действительно не нужно перезапускаться при изменении пропсов или состояния компонента.
Чтобы изменить зависимости, измените код¶
Возможно, вы заметили закономерность в своем рабочем процессе:
- Сначала вы изменяете код вашего Эффекта или то, как объявлены ваши реактивные значения.
- Затем, следуя за линтером, вы настраиваете зависимости, чтобы соответствовать измененному коду.
- Если список зависимостей вас не устраивает, вы возвращаетесь к первому шагу (и снова изменяете код).
Последняя часть очень важна. Если вы хотите изменить зависимости, сначала измените окружающий код. Вы можете думать о списке зависимостей как о списке всех реактивных значений, используемых кодом вашего Эффекта Вы не выбираете, что включить в этот список. Список описывает ваш код. Чтобы изменить список зависимостей, измените код.
Это может показаться похожим на решение уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно "найти" код, соответствующий этой цели. Не все находят решение уравнений забавным, и то же самое можно сказать о написании Effects! К счастью, существует список общих рецептов, которые вы можете попробовать ниже.
Внимание
Если у вас есть существующая кодовая база, возможно, у вас есть Effects, которые подавляют линтер, например, так:
1 2 3 4 5 |
|
Когда зависимости не соответствуют коду, очень высок риск появления ошибок. Подавляя линтер, вы "лжете" React о значениях, от которых зависит ваш 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 |
|
Допустим, вы хотите запустить эффект "только при монтировании". Вы прочитали, что пустые ([]
) зависимости делают это, поэтому вы решили проигнорировать линтер и принудительно указали []
в качестве зависимостей.
Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую с помощью двух кнопок. Однако, поскольку вы "соврали" React, что этот Effect ни от чего не зависит, React вечно продолжает использовать функцию onTick
из начального рендера. Во время этого рендера count
был 0
и increment
был 1
. Вот почему onTick
из этого рендера всегда вызывает setCount(0 + 1)
каждую секунду, и вы всегда видите 1
. Подобные ошибки труднее исправить, когда они распространяются на несколько компонентов.
Всегда есть лучшее решение, чем игнорирование linter! Чтобы исправить этот код, вам нужно добавить onTick
в список зависимостей. (Чтобы гарантировать, что интервал устанавливается только один раз, сделайте onTick
событием эффекта.)
Мы рекомендуем рассматривать ошибку dependency lint как ошибку компиляции. Если вы не подавите ее, вы никогда не увидите ошибок, подобных этой. Остальная часть этой страницы документирует альтернативы для этого и других случаев.
Удаление ненужных зависимостей¶
Каждый раз, когда вы изменяете зависимости Эффекта, чтобы отразить код, посмотрите на список зависимостей. Имеет ли смысл повторно запускать Эффект при изменении любой из этих зависимостей? Иногда ответ будет "нет":
- Вы можете захотеть повторно выполнить различные части вашего Эффекта при различных условиях.
- Вы можете захотеть прочитать только последнее значение какой-то зависимости вместо того, чтобы "реагировать" на ее изменения.
- Зависимость может меняться слишком часто непреднамеренно, потому что это объект или функция.
Чтобы найти правильное решение, вам нужно ответить на несколько вопросов о вашем Effect. Давайте пройдемся по ним.
Должен ли этот код переместиться в обработчик событий?¶
Первое, о чем вы должны подумать, это о том, должен ли этот код вообще быть Эффектом.
Представьте себе форму. При отправке вы устанавливаете переменную состояния submitted
в значение true
. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику в Effect, который "реагирует" на то, что submitted
стала true
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Позже вы захотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку theme
объявлена в теле компонента, она является реактивным значением, поэтому вы добавляете ее как зависимость:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Сделав это, вы ввели ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темной и светлой темами. Тема изменится, Эффект запустится заново и снова покажет то же самое уведомление!
Проблема в том, что это вообще не должно быть Эффектом. Вы хотите отправить POST-запрос и показать уведомление в ответ на отправку формы, что является определенным взаимодействием. Чтобы выполнить некоторый код в ответ на определенное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик события:
1 2 3 4 5 6 7 8 9 10 11 |
|
Теперь, когда код находится в обработчике событий, он не является реактивным - он будет выполняться только тогда, когда пользователь отправит форму. Читайте больше о выборе между обработчиками событий и Эффектами и как удалить ненужные Эффекты.
Ваш Эффект делает несколько несвязанных вещей?¶
Следующий вопрос, который вы должны задать себе, - не делает ли ваш Эффект несколько несвязанных вещей.
Представьте, что вы создаете форму доставки, в которой пользователю нужно выбрать город и область. Вы получаете список cities
с сервера в соответствии с выбранной country
, чтобы показать их в выпадающем списке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Это хороший пример получения данных в эффекте Вы синхронизируете состояние cities
с сетью в соответствии с параметром country
. Вы не можете сделать это в обработчике событий, потому что вам нужно получить данные, как только отображается ShippingForm
и всякий раз, когда изменяется country
(независимо от того, какое взаимодействие это вызывает).
Теперь предположим, что вы добавляете второе поле выбора для районов города, которое должно получить areas
для текущего выбранного city
. Вы можете начать с добавления второго вызова fetch
для списка областей внутри того же 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 |
|
Однако, поскольку Эффект теперь использует переменную состояния city
, вам пришлось добавить city
в список зависимостей. Это, в свою очередь, создало проблему: когда пользователь выберет другой город, Эффект запустится заново и вызовет fetchCities(country)
. В результате вы будете без необходимости многократно перевыполнять список городов.
Проблема этого кода в том, что вы синхронизируете две разные несвязанные вещи:.
- Вы хотите синхронизировать состояние
cities
с сетью на основе пропсаcountry
. - Вы хотите синхронизировать состояние
areas
с сетью на основе состоянияcity
.
Разделите логику на два Effects, каждый из которых реагирует на пропс, с которым ему нужно синхронизироваться:
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 |
|
Теперь первый Эффект запускается повторно только при изменении страны
, а второй Эффект запускается повторно при изменении города
. Вы разделили их по цели: две разные вещи синхронизируются двумя разными Эффектами. Два отдельных Эффекта имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно вызывать друг друга.
Окончательный код длиннее исходного, но разделение этих Эффектов все равно правильное. Каждый Эффект должен представлять независимый процесс синхронизации В этом примере удаление одного Эффекта не нарушает логику другого Эффекта. Это означает, что они синхронизируют разные вещи, и хорошо бы их разделить. Если вас беспокоит дублирование, вы можете улучшить этот код, извлекая повторяющуюся логику в пользовательский хук.
Читаете ли вы какое-то состояние для вычисления следующего состояния?¶
Этот Эффект обновляет переменную состояния messages
вновь созданным массивом каждый раз, когда приходит новое сообщение:
1 2 3 4 5 6 7 8 9 10 11 |
|
Она использует переменную messages
для создания нового массива, начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку messages
- это реактивное значение, считываемое Эффектом, оно должно быть зависимым:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
А если сделать messages
зависимостью, то возникает проблема.
Каждый раз, когда вы получаете сообщение, setMessages()
вызывает повторное отображение компонента с новым массивом messages
, который включает полученное сообщение. Однако, поскольку этот Эффект теперь зависит от messages
, это также пересинхронизирует Эффект. Таким образом, каждое новое сообщение будет заставлять чат заново соединяться. Пользователю это не понравится!
Чтобы решить эту проблему, не читайте messages
внутри Эффекта. Вместо этого, передайте функцию updater в setMessages
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Обратите внимание, что теперь ваш Effect вообще не читает переменную messages
. Вам нужно только передать функцию обновления, например msgs => [...msgs, receivedMessage]
. React поместит вашу функцию обновления в очередь и предоставит ей аргумент msgs
во время следующего рендера. Поэтому самому Эффекту больше не нужно зависеть от messages
. В результате этого исправления получение сообщения в чате больше не будет заставлять чат переподключаться.
Хотите ли вы читать значение, не "реагируя" на его изменения?¶
В процессе разработки
В этом разделе описывается экспериментальный API, который еще не был выпущен в стабильной версии React.
Предположим, что вы хотите проигрывать звук, когда пользователь получает новое сообщение, если isMuted
не равно true
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Поскольку ваш Effect теперь использует isMuted
в своем коде, вы должны добавить его в зависимости:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Проблема заключается в том, что каждый раз, когда isMuted
изменяется (например, когда пользователь нажимает кнопку "Muted"), Effect будет повторно синхронизироваться и снова подключаться к чату. Это не является желаемым пользовательским опытом! (В этом примере даже отключение линтера не поможет - если вы это сделаете, isMuted
"застрянет" со своим старым значением).
Чтобы решить эту проблему, вам нужно извлечь из эффекта логику, которая не должна быть реактивной. Вы не хотите, чтобы этот Эффект "реагировал" на изменения в isMuted
. Переместите эту нереактивную часть логики в событие эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
События эффектов позволяют разделить эффект на реактивные части (которые должны "реагировать" на реактивные значения, такие как roomId
и их изменения) и нереактивные части (которые считывают только последние значения, например, onMessage
считывает isMuted
). Теперь, когда вы читаете isMuted
внутри события эффекта, оно не должно быть зависимым от вашего эффекта. В результате, чат не будет переподключаться, когда вы переключаете настройку "Muted", решая первоначальную проблему!
Обертывание обработчика события из пропса
Вы можете столкнуться с подобной проблемой, когда ваш компонент получает обработчик события в качестве пропса:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Предположим, что родительский компонент передает различную функцию onReceiveMessage
при каждом рендере:
1 2 3 4 5 6 |
|
Поскольку onReceiveMessage
является зависимостью, это заставит Эффект повторно синхронизироваться после каждого повторного рендеринга родителя. Это заставило бы его заново подключаться к чату. Чтобы решить эту проблему, оберните вызов в событие эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
События эффектов не являются реактивными, поэтому вам не нужно указывать их в качестве зависимостей. В результате чат больше не будет переподключаться, даже если родительский компонент передает функцию, которая отличается при каждом повторном рендере.
Разделение реактивного и нереактивного кода
В этом примере вы хотите регистрировать посещение при каждом изменении roomId
. Вы хотите включать текущее значение notificationCount
в каждый журнал, но вы не хотите, чтобы изменение notificationCount
вызывало событие журнала.
Решение снова состоит в том, чтобы разделить нереактивный код на события Effect Event:
1 2 3 4 5 6 7 8 9 10 |
|
Вы хотите, чтобы ваша логика была реактивной в отношении roomId
, поэтому вы считываете roomId
внутри вашего Effect. Однако вы не хотите, чтобы изменение notificationCount
регистрировало дополнительный визит, поэтому вы читаете notificationCount
внутри события Effect. Подробнее о чтении последних пропсов и состояния из эффектов с помощью событий эффектов
Меняется ли непреднамеренно какое-то реактивное значение?¶
Иногда вы хотите, чтобы ваш Эффект "реагировал" на определенное значение, но это значение меняется чаще, чем вам хотелось бы - и может не отражать никаких реальных изменений с точки зрения пользователя. Например, допустим, вы создаете объект options
в теле вашего компонента, а затем считываете этот объект внутри вашего Эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Этот объект объявлен в теле компонента, поэтому это реактивное значение Когда вы читаете реактивное значение, подобное этому, внутри Эффекта, вы объявляете его как зависимость. Это гарантирует, что ваш Эффект "реагирует" на его изменения:
1 2 3 4 5 6 7 |
|
Важно объявить его как зависимость! Это гарантирует, например, что если roomId
изменится, то ваш Effect заново подключится к чату с новыми options
. Однако в приведенном выше коде также есть проблема. Чтобы увидеть ее, попробуйте ввести данные в поле ввода в песочнице ниже и посмотрите, что произойдет в консоли:
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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
В приведенной выше песочнице ввод только обновляет переменную состояния message
. С точки зрения пользователя, это не должно влиять на соединение с чатом. Однако каждый раз, когда вы обновляете message
, ваш компонент перерисовывается. Когда ваш компонент пересматривается, код внутри него снова запускается с нуля.
Новый объект options
создается с нуля при каждом повторном рендеринге компонента ChatRoom
. React видит, что объект options
является отличным объектом от объекта options
, созданного во время последнего рендеринга. Поэтому он повторно синхронизирует ваш Effect (который зависит от options
), и чат снова подключается, когда вы набираете текст.
Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отдельными от всех остальных. Не имеет значения, что содержимое внутри них может быть одинаковым!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Зависимости от объектов и функций могут заставить ваш Эффект пересинхронизироваться чаще, чем вам нужно.
Поэтому, по возможности, старайтесь избегать объектов и функций в качестве зависимостей вашего Эффекта. Вместо этого попробуйте переместить их за пределы компонента, внутрь Эффекта или извлечь из них примитивные значения.
Перемещение статических объектов и функций за пределы компонента
Если объект не зависит от пропсов и состояния, вы можете переместить этот объект за пределы вашего компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Таким образом, вы доказываете линтеру, что он не реактивный. Он не может измениться в результате повторного рендеринга, поэтому ему не нужно быть зависимостью. Теперь повторное отображение ChatRoom
не заставит ваш Effect повторно синхронизироваться.
Это работает и для функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Поскольку createOptions
объявляется вне вашего компонента, это не реактивное значение. Поэтому его не нужно указывать в зависимостях вашего Эффекта, и поэтому он никогда не заставит ваш Эффект пересинхронизироваться.
Перемещение динамических объектов и функций внутри вашего Эффекта
Если ваш объект зависит от какого-то реактивного значения, которое может измениться в результате повторного рендеринга, например, параметр roomId
, вы не можете переместить его внутрь вашего компонента. Однако вы можете переместить его создание внутрь кода вашего Эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Теперь, когда options
объявлено внутри вашего Эффекта, оно больше не является зависимостью вашего Эффекта. Вместо этого, единственным реактивным значением, используемым вашим Эффектом, является roomId
. Поскольку roomId
не является объектом или функцией, вы можете быть уверены, что оно не будет непреднамеренно отличаться. В JavaScript числа и строки сравниваются по их содержанию:
1 2 3 4 5 6 7 8 |
|
Благодаря этому исправлению чат больше не переподключается, если вы редактируете входные данные:
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 |
|
Однако он делает повторное подключение, когда вы изменяете выпадающий roomId
, как вы и ожидали.
Это работает и для функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Вы можете писать свои собственные функции для группировки частей логики внутри вашего Эффекта. Если вы также объявляете их внутри вашего Эффекта, они не являются реактивными значениями, и поэтому им не нужно быть зависимыми от вашего Эффекта.
Чтение примитивных значений из объектов
Иногда вы можете получить объект из пропса:
1 2 3 4 5 6 7 8 9 10 |
|
Риск здесь заключается в том, что родительский компонент создаст объект во время рендеринга:
1 2 3 4 5 6 7 |
|
Это приведет к тому, что ваш Эффект будет подключаться заново каждый раз, когда родительский компонент будет перестраиваться. Чтобы исправить это, считывайте информацию из объекта вне Эффекта и избегайте зависимостей между объектом и функцией:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Логика становится немного повторяющейся (вы считываете некоторые значения из объекта вне Эффекта, а затем создаете объект с теми же значениями внутри Эффекта). Но это позволяет четко определить, от какой информации на самом деле зависит ваш Эффект. Если объект непреднамеренно пересоздается родительским компонентом, чат не будет переподключаться. Однако, если options.roomId
или options.serverUrl
действительно отличаются, чат будет переподключен.
Вычисление примитивных значений из функций
Тот же подход может работать и для функций. Например, предположим, что родительский компонент передает функцию:
1 2 3 4 5 6 7 8 9 |
|
Чтобы не делать его зависимым (и не заставлять его переподключаться при повторных рендерах), вызывайте его вне Эффекта. Это даст вам значения roomId
и serverUrl
, которые не являются объектами, и которые вы можете прочитать изнутри вашего Эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Это работает только для чистых функций, потому что их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы ее изменения повторно синхронизировали ваш Эффект, оберните ее в событие Эффекта.
Итоги
- Зависимости всегда должны соответствовать коду.
- Если вы недовольны своими зависимостями, вам нужно отредактировать код.
- Подавление линтера приводит к очень запутанным ошибкам, и вы всегда должны избегать этого.
- Чтобы удалить зависимость, нужно "доказать" линтеру, что она не нужна.
- Если какой-то код должен выполняться в ответ на определенное взаимодействие, перенесите его в обработчик событий.
- Если разные части вашего Эффекта должны запускаться повторно по разным причинам, разделите его на несколько Эффектов.
- Если вы хотите обновить некоторое состояние на основе предыдущего состояния, передайте функцию обновления.
- Если вы хотите прочитать последнее значение, не "реагируя" на него, извлеките событие Effect Event из вашего Эффекта.
- В JavaScript объекты и функции считаются разными, если они были созданы в разное время.
- Старайтесь избегать зависимостей между объектами и функциями. Перенесите их за пределы компонента или внутрь Эффекта.
Задачи¶
1. Фиксировать интервал сброса¶
Этот Эффект устанавливает интервал, который тикает каждую секунду. Вы заметили, что происходит что-то странное: кажется, что интервал уничтожается и создается заново каждый раз, когда он тикает. Исправьте код так, чтобы интервал не создавался постоянно заново.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Показать подсказку
Кажется, что код этого Эффекта зависит от count
. Есть ли способ избежать этой зависимости? Должен быть способ обновить состояние count
на основе его предыдущего значения без добавления зависимости от этого значения.
Показать решение
Вы хотите обновить состояние count
до count + 1
изнутри Эффекта. Однако это заставит ваш Эффект зависеть от count
, который меняется с каждым тиком, и поэтому ваш интервал создается заново на каждом тике.
Чтобы решить эту проблему, используйте функцию updater и напишите setCount(c => c + 1)
вместо setCount(count + 1)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Вместо чтения count
внутри Effect, вы передаете React инструкцию c => c + 1
("увеличить это число!"). React применит ее при следующем рендере. А поскольку вам больше не нужно считывать значение count
внутри вашего Эффекта, вы можете держать зависимости вашего Эффекта пустыми ([]
). Это предотвратит повторное создание интервала в каждом тике вашего Эффекта.
2. Исправление ретриггерной анимации¶
В этом примере, когда вы нажимаете кнопку "Показать", появляется приветственное сообщение. Анимация длится секунду. При нажатии кнопки "Убрать" приветственное сообщение сразу же исчезает. Логика анимации затухания реализована в файле animation.js
в виде обычного JavaScript animation loop. Вам не нужно изменять эту логику. Вы можете обращаться с ней как со сторонней библиотекой. Ваш Effect создает экземпляр FadeInAnimation
для узла DOM, а затем вызывает start(duration)
или stop()
для управления анимацией. "Длительность" контролируется ползунком. Отрегулируйте ползунок и посмотрите, как изменится анимация.
Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время, когда вы перемещаете ползунок, управляющий переменной состояния duration
, он перезапускает анимацию. Измените поведение так, чтобы Эффект не "реагировал" на переменную duration
. Когда вы нажимаете "Show", Эффект должен использовать текущую duration
на ползунке. Однако перемещение ползунка само по себе не должно запускать анимацию.
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 |
|
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 |
|
Показать подсказку
Есть ли внутри Эффекта строка кода, которая не должна быть реактивной? Как вы можете переместить нереактивный код из Эффекта?
Показать решение
Ваш Эффект должен считывать последнее значение duration
, но вы не хотите, чтобы он "реагировал" на изменения duration
. Вы используете duration
для запуска анимации, но запуск анимации не является реактивным. Извлеките нереактивную строку кода в событие эффекта и вызовите эту функцию из вашего эффекта.
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 |
|
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 |
|
События эффектов, такие как onAppear
, не являются реактивными, поэтому вы можете считать duration
внутри, не перезапуская анимацию.
3. Исправление переподключающегося чата¶
В этом примере каждый раз, когда вы нажимаете кнопку "Toggle theme", чат переподключается. Почему это происходит? Исправьте ошибку, чтобы чат переподключался только тогда, когда вы редактируете URL сервера или выбираете другой чат.
Относитесь к chat.js
как к внешней сторонней библиотеке: вы можете обратиться к ней, чтобы проверить ее API, но не редактируйте ее.
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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
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 |
|
Показать подсказку
Есть несколько способов исправить это, но в конечном итоге вы хотите избежать наличия объекта в качестве зависимости.
Показать решение
Ваш Эффект запускается повторно, потому что он зависит от объекта options
. Объекты могут быть созданы заново непреднамеренно, вы должны стараться избегать их в качестве зависимостей ваших Эффектов, когда это возможно.
Наименее инвазивное решение - это считывать roomId
и serverUrl
прямо вне Эффекта, а затем сделать Эффект зависимым от этих примитивных значений (которые не могут измениться непреднамеренно). Внутри эффекта создайте объект и передайте его в createConnection
:
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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
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 |
|
Еще лучше было бы заменить пропс объекта options
на более конкретные пропсы roomId
и serverUrl
:
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 |
|
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 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Придерживаясь по возможности примитивных пропсов, легче оптимизировать компоненты в дальнейшем.
4. Исправление переподключающегося чата, снова¶
Этот пример подключается к чату либо с шифрованием, либо без него. Переключите флажок и обратите внимание на разные сообщения в консоли, когда шифрование включено и выключено. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к чату, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме.
В данном примере чат подключается заново каждый раз, когда вы пытаетесь сменить тему. Исправьте это. После исправления смена темы не должна переподключать чат, но переключение настроек шифрования или смена комнаты должны переподключать.
Не изменяйте никакой код в файле chat.js
. Кроме этого, вы можете изменять любой код, если он приводит к такому же поведению. Например, вы можете счесть полезным изменить, какие пропсы передаются вниз.
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 70 71 72 73 74 75 76 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Показать подсказку
Вы передаете две функции: onMessage
и createConnection
. Обе они создаются с нуля каждый раз при повторном рендеринге App
. Они рассматриваются как новые значения каждый раз, поэтому они повторно запускают ваш Effect.
Одна из этих функций является обработчиком событий. Знаете ли вы какой-нибудь способ вызвать Эффект из обработчика события, не "реагируя" на новые значения функции обработчика события? Это было бы очень удобно!
Еще одна из этих функций существует только для того, чтобы передать некоторое состояние импортированному методу API. Действительно ли эта функция необходима? Какая важная информация передается? Возможно, вам нужно перенести некоторые импорты из App.js
в ChatRoom.js
.
Показать решение
Существует не один правильный способ решения этой проблемы, но вот одно из возможных решений.
В оригинальном примере переключение темы вызывало создание и передачу различных функций onMessage
и createConnection
. Поскольку эффект зависел от этих функций, чат переподключался каждый раз, когда вы переключали тему.
Чтобы решить проблему с onMessage
, нужно было обернуть ее в событие эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
В отличие от пропса onMessage
, событие эффекта onReceiveMessage
не является реактивным. Поэтому оно не должно быть зависимым от вашего эффекта. В результате, изменения в onMessage
не приведут к повторному подключению чата.
Вы не можете сделать то же самое с createConnection
, потому что он должен быть реактивным. Вы хотите, чтобы Эффект повторно срабатывал, если пользователь переключается между зашифрованным и незашифрованным соединением, или если пользователь переключает текущую комнату. Однако, поскольку createConnection
- это функция, вы не можете проверить, изменилась ли информация, которую она считывает, фактически или нет. Чтобы решить эту проблему, вместо передачи createConnection
вниз из компонента App
, передайте необработанные значения roomId
и isEncrypted
:
1 2 3 4 5 6 7 8 9 10 |
|
Теперь вы можете переместить функцию createConnection
внутрь Effect вместо того, чтобы передавать ее из App
:
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 |
|
После этих двух изменений ваш 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 |
|
В результате чат переподключается только тогда, когда меняется что-то значимое (roomId
или isEncrypted
):
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 |
|
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 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Источник — https://react.dev/learn/removing-effect-dependencies