Отделение событий от эффектов¶
Обработчики событий запускаются повторно только при повторном выполнении того же взаимодействия. В отличие от обработчиков событий, эффекты повторно синхронизируются, если какое-либо значение, которое они считывают, например пропс или переменная состояния, отличается от того, что было во время последнего рендеринга. Иногда требуется сочетание обоих типов поведения: Эффект, который повторно запускается в ответ на некоторые значения, но не на другие. На этой странице вы узнаете, как это сделать.
Вы узнаете
- Как выбрать между обработчиком событий и эффектом
- Почему эффекты являются реактивными, а обработчики событий - нет
- Что делать, если вы хотите, чтобы часть кода вашего Эффекта не была реактивной
- Что такое события эффектов и как извлекать их из эффектов
- Как считывать последние пропсы и состояние из Эффектов с помощью событий эффектов
Выбор между обработчиками событий и Эффектами¶
Во-первых, давайте вспомним разницу между обработчиками событий и Эффектами.
Представьте, что вы реализуете компонент чата. Ваши требования выглядят следующим образом:
- Ваш компонент должен автоматически подключаться к выбранному чату.
- Когда вы нажимаете кнопку "Отправить", он должен отправлять сообщение в чат.
Допустим, вы уже реализовали код для них, но не уверены, куда его поместить. Следует ли вам использовать обработчики событий или эффекты? Каждый раз, когда вам нужно ответить на этот вопрос, подумайте почему код должен выполняться
Обработчики событий запускаются в ответ на конкретные взаимодействия¶
С точки зрения пользователя, отправка сообщения должна произойти потому что была нажата конкретная кнопка "Отправить". Пользователь будет очень расстроен, если вы отправите его сообщение в любое другое время или по любой другой причине. Вот почему отправка сообщения должна быть обработчиком событий. Обработчики событий позволяют вам обрабатывать определенные взаимодействия:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
С обработчиком событий вы можете быть уверены, что sendMessage(message)
будет только если пользователь нажмет на кнопку.
Эффекты запускаются всякий раз, когда необходима синхронизация¶
Вспомните, что вам также нужно, чтобы компонент был подключен к чату. Где находится этот код?
Причиной для запуска этого кода не является какое-то конкретное взаимодействие. Неважно, почему или как пользователь перешел на экран чата. Теперь, когда они смотрят на него и могут взаимодействовать с ним, компонент должен оставаться подключенным к выбранному серверу чата. Даже если компонент чата был начальным экраном вашего приложения, и пользователь не выполнял никаких действий, вам все равно потребуется подключение. Вот почему это Эффект:
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 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 |
|
Реактивные значения и реактивная логика¶
Интуитивно можно сказать, что обработчики событий всегда запускаются "вручную", например, при нажатии на кнопку. Эффекты, с другой стороны, являются "автоматическими": они запускаются и перезапускаются так часто, как это необходимо для сохранения синхронизации.
Есть более точный способ подумать об этом.
пропсы, состояния и переменные, объявленные в теле компонента, называются реактивными значениями. В этом примере serverUrl
не является реактивным значением, но roomId
и message
являются таковыми. Они участвуют в потоке данных рендеринга:
1 2 3 4 5 6 7 |
|
Реактивные значения, такие как эти, могут измениться в результате повторного рендеринга. Например, пользователь может изменить сообщение
или выбрать другой номер комнаты
в выпадающем списке. Обработчики событий и эффекты по-разному реагируют на изменения:
- Логика внутри обработчиков событий не реактивна. Она не будет запущена снова, пока пользователь не выполнит то же самое действие (например, щелчок) снова. Обработчики событий могут считывать реактивные значения, не "реагируя" на их изменения.
- Если ваш эффект считывает реактивное значение, вы должны указать его как зависимость, то, если при повторном рендеринге это значение изменится, React запустит логику вашего эффекта заново с новым значением.
Давайте вернемся к предыдущему примеру, чтобы проиллюстрировать эту разницу.
Логика внутри обработчиков событий не является реактивной¶
Взгляните на эту строку кода. Должна ли эта логика быть реактивной или нет?
1 2 3 |
|
С точки зрения пользователя, изменение message
не означает, что он хочет отправить сообщение. Это означает только то, что пользователь набирает текст. Другими словами, логика, отправляющая сообщение, не должна быть реактивной. Она не должна запускаться снова только потому, что реактивное значение изменилось. Вот почему она должна находиться в обработчике событий:
1 2 3 |
|
Обработчики событий не являются реактивными, поэтому sendMessage(message)
будет выполняться только тогда, когда пользователь нажмет кнопку Send.
Логика внутри Effects является реактивной¶
Теперь давайте вернемся к этим строкам:
1 2 3 4 |
|
С точки зрения пользователя, изменение roomId
означает, что он хочет подключиться к другой комнате. Другими словами, логика подключения к комнате должна быть реактивной. Вы хотите, чтобы эти строки кода "шли в ногу" с реактивным значением, и запускались снова, если это значение изменилось. Вот почему это должно быть в Эффекте:
1 2 3 4 5 6 7 |
|
Эффекты являются реактивными, поэтому createConnection(serverUrl, roomId)
и connection.connect()
будут выполняться для каждого отдельного значения roomId
. Ваш эффект сохраняет соединение чата синхронизированным с текущей выбранной комнатой.
Извлечение нереактивной логики из эффектов¶
Ситуация усложняется, когда вы хотите смешать реактивную логику с нереактивной.
Например, представьте, что вы хотите показать уведомление, когда пользователь подключается к чату. Вы считываете текущую тему (темную или светлую) из пропса, чтобы показать уведомление нужного цвета:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Однако theme
является реактивным значением (оно может меняться в результате перерисовки), и каждое реактивное значение, считываемое Эффектом, должно быть объявлено его зависимостью Теперь вы должны указать theme
как зависимость вашего Эффекта:
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
|
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 |
|
При изменении roomId
чат переподключается, как и следовало ожидать. Но поскольку theme
также является зависимостью, чат также переподключается каждый раз, когда вы переключаетесь между темной и светлой темой. Это не здорово!
Другими словами, вы не хотите, чтобы эта строка была реактивной, даже если она находится внутри Effect (который является реактивным):
1 2 3 |
|
Вам нужен способ отделить эту нереактивную логику от реактивного Эффекта вокруг нее.
Объявление события эффекта¶
В разработке
Этот раздел описывает экспериментальный API, который еще не был выпущен в стабильной версии React.
Используйте специальный хук под названием useEffectEvent
, чтобы извлечь эту нереактивную логику из вашего Эффекта:
1 2 3 4 5 6 7 8 |
|
Здесь onConnected
называется событием эффекта. Это часть логики вашего эффекта, но она ведет себя гораздо больше как обработчик событий. Логика внутри него не является реактивной, и он всегда "видит" последние значения ваших пропсов и состояния.
Теперь вы можете вызывать событие Эффекта onConnected
изнутри вашего Эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Это решает проблему. Обратите внимание, что вам пришлось удалить onConnected
из списка зависимостей вашего Эффекта. События эффектов не являются реактивными и должны быть исключены из зависимостей..
Проверьте, что новое поведение работает так, как вы ожидаете:
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 |
|
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 |
|
События эффектов можно считать очень похожими на обработчики событий. Основное различие заключается в том, что обработчики событий запускаются в ответ на действия пользователя, в то время как События эффектов запускаются вами из Эффектов. События эффектов позволяют вам "разорвать цепь" между реактивностью эффектов и кодом, который не должен быть реактивным.
Чтение последних пропсов и состояния с помощью событий эффектов¶
В разработке
Этот раздел описывает экспериментальный API, который еще не был выпущен в стабильной версии React.
События Effect Events позволяют исправить многие паттерны, где может возникнуть соблазн подавить линтер зависимостей.
Например, у вас есть эффект для регистрации посещений страницы:
1 2 3 4 5 6 |
|
Позже вы добавляете несколько маршрутов на свой сайт. Теперь ваш компонент Page
получает пропс url
с текущим путем. Вы хотите передать url
как часть вашего вызова logVisit
, но линтер зависимостей жалуется:
1 2 3 4 5 6 |
|
Подумайте, что вы хотите сделать с помощью кода. Вы хотите регистрировать отдельные посещения для разных URL, поскольку каждый URL представляет собой отдельную страницу. Другими словами, этот вызов logVisit
должен быть реактивным по отношению к url
. Вот почему в данном случае имеет смысл следовать указателю зависимостей и добавить url
в качестве зависимости:
1 2 3 4 5 6 |
|
Теперь предположим, что вы хотите включать количество товаров в корзине вместе с каждым посещением страницы:
1 2 3 4 5 6 7 8 9 |
|
Вы использовали numberOfItems
внутри Effect, поэтому линтер просит вас добавить его в качестве зависимости. Однако, вы не хотите, чтобы вызов logVisit
был реактивным по отношению к numberOfItems
. Если пользователь положил что-то в корзину, и numberOfItems
изменилось, это не означает, что пользователь снова посетил страницу. Другими словами, посещение страницы - это, в некотором смысле, "событие". Оно происходит в определенный момент времени.
Разделите код на две части:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Здесь onVisit
- это событие эффекта. Код внутри него не является реактивным. Вот почему вы можете использовать numberOfItems
(или любое другое реактивное значение!), не беспокоясь о том, что оно заставит окружающий код повторно выполняться при изменениях.
С другой стороны, сам Эффект остается реактивным. Код внутри Эффекта использует пропс url
, поэтому Эффект будет запускаться заново после каждого повторного рендеринга с другим url
. Это, в свою очередь, вызовет событие эффекта onVisit
.
В результате вы будете вызывать logVisit
для каждого изменения url
, и всегда считывать последнее numberOfItems
. Однако, если numberOfItems
изменится сам по себе, это не приведет к повторному выполнению кода.
Вам может быть интересно, можно ли вызвать функцию onVisit()
без аргументов и прочитать url
внутри нее:
1 2 3 4 5 6 7 |
|
Это сработает, но лучше передавать url
в событие Effect Event явно. Передавая url
в качестве аргумента в событие Effect Event, вы говорите, что посещение страницы с другим url
представляет собой отдельное "событие" с точки зрения пользователя. visitedUrl
является частью произошедшего "события":
1 2 3 4 5 6 7 |
|
Поскольку событие вашего эффекта явно "запрашивает" visitedUrl
, теперь вы не сможете случайно удалить url
из зависимостей эффекта. Если вы удалите зависимость url
(в результате чего посещение разных страниц будет считаться за одно), линтер предупредит вас об этом. Вы хотите, чтобы onVisit
был реактивным по отношению к url
, поэтому вместо того, чтобы читать url
внутри (где он не будет реактивным), вы передаете его из вашего Эффекта.
Это становится особенно важным, если внутри Эффекта есть какая-то асинхронная логика:
1 2 3 4 5 6 7 8 9 |
|
Здесь url
внутри onVisit
соответствует последнему url
(который мог уже измениться), но visitedUrl
соответствует url
, который первоначально вызвал запуск этого Effect (и этого вызова onVisit
).
Можно ли вместо этого подавить линтер зависимостей?
В существующих кодовых базах иногда можно встретить подавление правила lint следующим образом:
1 2 3 4 5 6 7 8 9 10 11 |
|
После того, как useEffectEvent
станет стабильной частью React, мы рекомендуем никогда не подавлять линтер.
Первый недостаток подавления правила заключается в том, что React больше не будет предупреждать вас, когда ваш Эффект должен "отреагировать" на новую реактивную зависимость, которую вы добавили в свой код. В предыдущем примере вы добавили url
к зависимостям потому что React напомнил вам сделать это. Если вы отключите линтер, вы больше не будете получать такие напоминания для любых будущих правок этого Эффекта. Это приводит к ошибкам.
Вот пример запутанной ошибки, вызванной подавлением линтера. В этом примере функция handleMove
должна прочитать текущее значение переменной состояния canMove
, чтобы решить, должна ли точка следовать за курсором. Однако, canMove
всегда true
внутри handleMove
.
Вы можете понять, почему?
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 |
|
Проблема с этим кодом заключается в подавлении линтера зависимостей. Если вы уберете подавление, то увидите, что этот Effect должен зависеть от функции handleMove
. Это имеет смысл: handleMove
объявлена внутри тела компонента, что делает ее реактивным значением. Каждое реактивное значение должно быть указано как зависимость, иначе оно может устареть со временем!
Автор оригинального кода "соврал" React, сказав, что Effect не зависит ([]
) ни от каких реактивных значений. Именно поэтому React не пересинхронизировал Effect после изменения canMove
(и handleMove
вместе с ним). Поскольку React не пересинхронизировал Effect, handleMove
, прикрепленная в качестве слушателя, является функцией handleMove
, созданной во время первоначального рендеринга. Во время первоначального рендера canMove
было true
, поэтому handleMove
из первоначального рендера всегда будет видеть это значение.
Если вы никогда не подавляете линтер, вы никогда не увидите проблем с устаревшими значениями.
С useEffectEvent
нет необходимости "врать" линтеру, и код работает так, как вы ожидаете:
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 |
|
Это не означает, что useEffectEvent
- это всегда правильное решение. Вы должны применять его только к тем строкам кода, которые вы не хотите, чтобы были реактивными. В приведенной выше песочнице вы не хотели, чтобы код Эффекта был реактивным в отношении canMove
. Вот почему имело смысл извлечь событие эффекта.
Читайте Удаление зависимостей эффектов о других правильных альтернативах подавления линтера.
Ограничения событий эффектов¶
В разработке
Этот раздел описывает экспериментальный API, который еще не был выпущен в стабильной версии React.
События эффектов очень ограничены в том, как вы можете их использовать:
- Вызывать их только изнутри Эффектов.
- Никогда не передавайте их другим компонентам или хукам.
Например, не объявляйте и не передавайте событие эффекта следующим образом:
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 16 17 18 19 20 21 22 |
|
События эффектов - это нереактивные "куски" кода вашего эффекта. Они должны находиться рядом с использующим их Эффектом.
Итого
- Обработчики событий запускаются в ответ на определенные взаимодействия.
- Эффекты запускаются всякий раз, когда требуется синхронизация.
- Логика внутри обработчиков событий не является реактивной.
- Логика внутри эффектов реактивна.
- Вы можете перенести нереактивную логику из Эффектов в События Эффектов.
- Вызывайте события эффектов только из самих эффектов.
- Не передавайте события эффектов другим компонентам или хукам.
Задачи¶
1. Исправление переменной, которая не обновляется¶
Этот компонент Timer
хранит переменную состояния count
, которая увеличивается каждую секунду. Значение, на которое она увеличивается, хранится в переменной состояния increment
. Вы можете управлять переменной increment
с помощью кнопок плюс и минус.
Однако, сколько бы раз вы ни нажали на кнопку с плюсом, счетчик все равно увеличивается на единицу каждую секунду. Что не так с этим кодом? Почему increment
всегда равен 1
в коде 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 скажет вам, что код этого Эффекта зависит от increment
, но вы "солгали" React, утверждая, что этот Эффект не зависит ни от каких реактивных значений ([]
). Добавьте increment
в массив зависимостей:
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 |
|
Теперь, когда increment
изменится, React пересинхронизирует ваш Effect, что перезапустит интервал.
2. Исправьте зависший счетчик¶
Этот компонент Timer
хранит переменную состояния count
, которая увеличивается каждую секунду. Значение, на которое она увеличивается, хранится в переменной состояния increment
, которой вы можете управлять с помощью кнопок плюс и минус. Например, попробуйте нажать кнопку плюс девять раз и заметите, что теперь count
увеличивается каждую секунду на десять, а не на один.
Есть небольшая проблема с этим пользовательским интерфейсом. Вы можете заметить, что если вы продолжаете нажимать на кнопки плюс или минус быстрее, чем один раз в секунду, то сам таймер как бы приостанавливается. Он возобновляется только после того, как пройдет секунда с момента последнего нажатия любой из кнопок. Выясните, почему это происходит, и устраните проблему, чтобы таймер тикал каждую секунду без перерывов.
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 |
|
Показать подсказку
Похоже, что Effect, который устанавливает таймер, "реагирует" на значение increment
. Действительно ли строка, которая использует текущее значение increment
для вызова setCount
должна быть реактивной?
Показать решение
Проблема в том, что код внутри Эффекта использует переменную состояния increment
. Поскольку она зависит от вашего Эффекта, каждое изменение increment
заставляет Эффект пересинхронизироваться, что приводит к очистке интервала. Если вы будете очищать интервал каждый раз, прежде чем он успеет сработать, то будет казаться, что таймер остановился.
Чтобы решить эту проблему, извлеките из Эффекта событие Эффекта onTick
:
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 |
|
Поскольку onTick
является событием Эффекта, код внутри него не является реактивным. Изменение increment
не вызывает никаких Эффектов.
3. Исправление нерегулируемой задержки¶
В этом примере вы можете настроить интервальную задержку. Она хранится в переменной состояния delay
, которая обновляется двумя кнопками. Однако, даже если вы будете нажимать кнопку "плюс 100 мс", пока delay
не станет равной 1000 миллисекунд (то есть секунде), вы заметите, что таймер все равно увеличивается очень быстро (каждые 100 мс). Как будто ваши изменения delay
игнорируются. Найдите и исправьте ошибку.
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 |
|
Показать подсказку
Код внутри Effect Events не является реактивным. Есть ли случаи, в которых вы хотели бы, чтобы вызов setInterval
выполнялся повторно?
Показать решение
Проблема приведенного выше примера заключается в том, что он извлек событие Effect Event под названием onMount
, не задумываясь о том, что на самом деле должен делать код. Вы должны извлекать события Effect Events только по определенной причине: когда вы хотите сделать часть вашего кода нереактивной. Однако вызов setInterval
должен быть реактивным по отношению к переменной состояния delay
. Если delay
меняется, вы хотите установить интервал с нуля! Чтобы исправить этот код, перенесите весь реактивный код обратно в 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
В целом, вы должны с подозрением относиться к функциям типа onMount
, которые сосредоточены на времени, а не на цели части кода. Поначалу это может показаться "более описательным", но это затуманивает ваш замысел. Как правило, события эффектов должны соответствовать чему-то, что происходит с точки зрения пользователя. Например, onMessage
, onTick
, onVisit
или onConnected
- хорошие названия событий эффектов. Код внутри них, скорее всего, не должен быть реактивным. С другой стороны, onMount
, onUpdate
, onUnmount
или onAfterRender
настолько общие, что в них легко случайно поместить код, который должен быть реактивным. Вот почему вы должны называть свои события Effect Events в соответствии с тем, что, по мнению пользователя, произошло, а не с тем, когда был запущен какой-то код.
4. Исправление отложенного уведомления¶
Когда вы присоединяетесь к чату, этот компонент показывает уведомление. Однако он не показывает уведомление немедленно. Вместо этого уведомление искусственно задерживается на две секунды, чтобы у пользователя была возможность осмотреться в пользовательском интерфейсе.
Это почти работает, но есть ошибка. Попробуйте быстро изменить выпадающий список с "общие" на "путешествия", а затем на "музыка". Если вы сделаете это достаточно быстро, вы увидите два уведомления (как и ожидалось!), но в обоих будет написано "Добро пожаловать в музыку".
Исправьте это так, чтобы при быстром переключении с "общего" на "путешествия" и затем на "музыку" вы видели два уведомления, первое из которых было бы "Добро пожаловать в путешествие", а второе - "Добро пожаловать в музыку". (Для дополнительной сложности, если вы уже сделали так, чтобы уведомления показывали правильные комнаты, измените код так, чтобы отображалось только последнее уведомление).
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 |
|
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 |
|
Показать подсказку
Ваш Эффект знает, к какой комнате он подключился. Есть ли какая-нибудь информация, которую вы могли бы передать в событие вашего Эффекта?
Показать решение
Внутри вашего события Effect Event, roomId
- это значение в момент вызова Effect Event..
Ваше Событие Эффекта вызывается с двухсекундной задержкой. Если вы быстро переключаетесь из комнаты для путешествий в музыкальную комнату, то к тому времени, когда появится уведомление комнаты для путешествий, roomId
уже будет "music"
. Вот почему оба уведомления говорят "Добро пожаловать в музыкальную комнату".
Чтобы решить эту проблему, вместо чтения последнего roomId
внутри события эффекта, сделайте его параметром события эффекта, как connectedRoomId
ниже. Затем передавайте roomId
из вашего Эффекта, вызывая onConnected(roomId)
:
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 |
|
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
был установлен на "travel"
(поэтому он подключился к комнате "travel"
), покажет уведомление для "travel"
. Эффект, у которого roomId
установлен в "music"
(поэтому он подключился к комнате "music"
), покажет уведомление для "music"
. Другими словами, connectedRoomId
приходит от вашего Эффекта (который является реактивным), в то время как theme
всегда использует последнее значение.
Чтобы решить дополнительную проблему, сохраните идентификатор таймаута уведомления и очистите его в функции очистки вашего Эффекта:
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 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Это гарантирует, что уже запланированные (но еще не отображенные) уведомления будут отменены, когда вы смените комнату.
Источник — https://react.dev/learn/separating-events-from-effects