<Activity>¶
<Activity> позволяет скрывать и восстанавливать интерфейс и внутреннее состояние дочерних элементов.
1 2 3 | |
Описание¶
<Activity>¶
Activity можно использовать, чтобы скрыть часть приложения:
1 2 3 | |
Когда граница Activity скрыта, React визуально скрывает её дочерние элементы с помощью CSS-свойства display: "none". Он также уничтожает их эффекты, очищая все активные подписки.
Пока граница скрыта, дочерние компоненты всё равно перерендериваются в ответ на новые пропсы, хотя и с более низким приоритетом, чем остальное содержимое.
Когда граница снова становится видимой, React показывает дочерние элементы с восстановленным прежним состоянием и заново создаёт их эффекты.
Таким образом, Activity можно считать механизмом рендеринга «фоновой активности». Вместо того чтобы полностью отбрасывать содержимое, которое, скорее всего, снова станет видимым, Activity позволяет сохранять и восстанавливать интерфейс и внутреннее состояние этого содержимого и при этом гарантировать, что у скрытого содержимого нет нежелательных побочных эффектов.
Пропсы¶
children: Интерфейс, который вы собираетесь показывать и скрывать.mode: Строка со значением'visible'или'hidden'. Если проп опущен, по умолчанию используется'visible'.
Предупреждения¶
- Если Activity отрендерен внутри ViewTransition и становится видимым в результате обновления, вызванного startTransition, активируется анимация
enterу ViewTransition. Если он становится скрытым, активируется его анимацияexit. - Скрытый Activity, который рендерит только текст, не выводит ничего: скрытый текст тоже не появляется, потому что нет соответствующего DOM-элемента, к которому можно применить изменение видимости. Например,
<Activity mode="hidden"><ComponentThatJustReturnsText /></Activity>не создаст никакого вывода в DOM дляconst ComponentThatJustReturnsText = () => "Hello, World!".<Activity mode="visible"><ComponentThatJustReturnsText /></Activity>отрендерит видимый текст.
Использование¶
Восстановление состояния скрытых компонентов¶
В React, когда нужно условно показать или скрыть компонент, его обычно монтируют или размонтируют в зависимости от этого условия:
1 2 3 | |
Но размонтирование компонента уничтожает его внутреннее состояние, а это нужно не всегда.
Когда вместо этого вы скрываете компонент границей Activity, React «сохранит» его состояние на потом:
1 2 3 | |
Так можно скрыть компоненты, а затем восстановить их в том состоянии, в котором они были раньше.
В следующем примере есть боковая панель с раскрывающимся разделом. Можно нажать «Overview», чтобы показать три пункта под ним. В основной области приложения тоже есть кнопка, которая скрывает и показывает боковую панель.
Попробуйте раскрыть раздел Overview, а затем закрыть и снова открыть боковую панель:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
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 19 20 21 22 23 24 25 26 27 28 29 | |
Раздел Overview всегда начинается свёрнутым. Поскольку мы размонтируем боковую панель, когда isShowingSidebar становится false, всё её внутреннее состояние теряется.
Это отличный случай для Activity. Мы можем сохранить внутреннее состояние боковой панели, даже когда визуально её скрываем.
Заменим условный рендеринг боковой панели границей Activity:
1 2 3 4 5 6 7 8 9 | |
и посмотрите на новое поведение:
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 23 24 | |
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 | |
Внутреннее состояние боковой панели теперь восстанавливается, без каких-либо изменений в её реализации.
Восстановление DOM скрытых компонентов¶
Поскольку границы Activity скрывают дочерние элементы с помощью display: none, DOM дочерних элементов тоже сохраняется, пока они скрыты. Это удобно, чтобы удерживать временное состояние в тех частях интерфейса, с которыми пользователь, скорее всего, снова будет взаимодействовать.
В этом примере на вкладке Contact есть <textarea>, куда пользователь может ввести сообщение. Если ввести текст, перейти на вкладку Home, а затем вернуться на вкладку Contact, черновик сообщения пропадёт:
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 | |
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 | |
Так происходит потому, что мы полностью размонтируем Contact в App. Когда вкладка Contact размонтируется, внутреннее состояние DOM элемента <textarea> теряется.
Если вместо этого использовать границу Activity, чтобы показывать и скрывать активную вкладку, можно сохранить состояние 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 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 | |
И снова граница Activity позволила сохранить внутреннее состояние вкладки Contact, не меняя её реализацию.
Предварительный рендеринг содержимого, которое, скорее всего, станет видимым¶
До сих пор мы видели, как Activity может скрывать содержимое, с которым пользователь уже взаимодействовал, не отбрасывая временное состояние этого содержимого.
Но границы Activity можно использовать и чтобы подготовить содержимое, которое пользователь ещё не видел:
1 2 3 | |
Когда граница Activity скрыта во время первоначального рендеринга, её дочерние элементы не будут видны на странице — но они всё равно будут отрендерены, хотя и с более низким приоритетом, чем видимое содержимое, и без монтирования их эффектов.
Такой предварительный рендеринг позволяет дочерним элементам заранее загрузить нужные код или данные, чтобы позже, когда граница Activity станет видимой, дочерние элементы появлялись быстрее и с меньшим временем загрузки.
Рассмотрим пример.
В этой демонстрации вкладка Posts загружает некоторые данные. Если нажать на неё, вы увидите фолбэк Suspense, пока данные загружаются:
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 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
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 | |
1 2 3 4 5 | |
Так происходит потому, что App не монтирует Posts, пока его вкладка не активна.
Если обновить App, чтобы показывать и скрывать активную вкладку границей Activity, Posts будет предварительно отрендерен при первой загрузке приложения и сможет загрузить свои данные до того, как станет видимым.
Попробуйте теперь нажать на вкладку Posts:
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 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
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 | |
1 2 3 4 5 | |
Posts смог подготовиться к более быстрому рендерингу благодаря скрытой границе Activity.
Предварительный рендеринг компонентов скрытыми границами Activity — мощный способ сократить время загрузки тех частей интерфейса, с которыми пользователь, скорее всего, взаимодействует следующими.
Примечание
Во время предварительного рендеринга загружаются только данные, прочитанные из источника, который активирует границу Suspense, например промис, прочитанный с помощью use. Activity не обнаруживает данные, полученные внутри эффекта.
Ускорение взаимодействий во время загрузки страницы¶
В React есть внутренняя оптимизация производительности под названием Selective Hydration. Она гидратирует исходный HTML приложения частями, позволяя некоторым компонентам стать интерактивными, даже если другие компоненты на странице ещё не загрузили свой код или данные.
Границы Suspense участвуют в Selective Hydration, потому что естественным образом делят дерево компонентов на независимые друг от друга единицы:
1 2 3 4 5 6 7 8 9 10 11 | |
Здесь MessageComposer может быть полностью гидратирован во время первоначального рендеринга страницы, ещё до того, как Chats будет смонтирован и начнёт загружать свои данные.
Разбивая дерево компонентов на отдельные единицы, Suspense позволяет React гидратировать отрендеренный на сервере HTML частями, чтобы части приложения становились интерактивными как можно быстрее.
А как быть со страницами, которые не используют Suspense?
Возьмём такой пример с вкладками:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Здесь React должен гидратировать всю страницу за один раз. Если Home или Video рендерятся медленнее, кнопки вкладок могут казаться неотзывчивыми во время гидратации.
Добавление Suspense вокруг активной вкладки решило бы это:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
...но это также изменит интерфейс, потому что фолбэк Placeholder будет показан при первоначальном рендеринге.
Вместо этого можно использовать Activity. Поскольку границы Activity показывают и скрывают свои дочерние элементы, они уже естественным образом делят дерево компонентов на независимые единицы. И так же, как Suspense, эта возможность позволяет им участвовать в Selective Hydration.
Обновим пример и обернём активную вкладку границами Activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Теперь исходный HTML, отрендеренный на сервере, выглядит так же, как в первоначальной версии, но благодаря Activity React может сначала гидратировать кнопки вкладок, ещё до того, как смонтирует Home или Video.
Таким образом, помимо скрытия и показа содержимого, границы Activity улучшают производительность приложения во время гидратации: React узнаёт, какие части страницы могут стать интерактивными по отдельности.
И даже если страница никогда не скрывает часть своего содержимого, можно добавить всегда видимые границы Activity, чтобы улучшить производительность гидратации:
1 2 3 4 5 6 7 8 9 10 11 | |
Устранение неполадок¶
У скрытых компонентов есть нежелательные побочные эффекты¶
Граница Activity скрывает своё содержимое, устанавливая display: none у дочерних элементов и очищая все их эффекты. Поэтому большинство корректных компонентов React, которые правильно очищают свои побочные эффекты, уже устойчивы к скрытию через Activity.
Но бывают ситуации, когда скрытый компонент ведёт себя иначе, чем размонтированный. Главное отличие: DOM скрытого компонента не уничтожается, поэтому любые побочные эффекты этого DOM сохраняются и после того, как компонент скрыт.
В качестве примера рассмотрим тег <video>. Обычно ему не нужна очистка, потому что даже если видео воспроизводится, размонтирование тега останавливает видео и звук в браузере. Попробуйте воспроизвести видео, а затем нажать Home в этой демонстрации:
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 | |
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
Видео останавливается, как и ожидалось.
Теперь допустим, мы хотим сохранить таймкод, на котором пользователь остановился, чтобы при возврате на вкладку видео не начиналось сначала.
Это отличный случай для Activity!
Обновим App, чтобы скрывать неактивную вкладку скрытой границей Activity вместо размонтирования, и посмотрим, как демонстрация ведёт себя теперь:
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 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
Ой! Видео и звук продолжают воспроизводиться даже после скрытия, потому что элемент <video> вкладки всё ещё находится в DOM.
Чтобы это исправить, можно добавить эффект с функцией очистки, которая ставит видео на паузу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Мы вызываем useLayoutEffect вместо useEffect, потому что концептуально код очистки связан с визуальным скрытием интерфейса компонента. Если использовать обычный эффект, код может задержаться, например, из-за повторной приостановки границы Suspense или View Transition.
Посмотрим на новое поведение. Попробуйте воспроизвести видео, переключиться на вкладку Home, а затем вернуться на вкладку Video:
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 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 | |
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 | |
Отлично работает! Функция очистки гарантирует, что видео остановится, если его когда-либо скроет граница Activity, и, что ещё лучше, поскольку тег <video> никогда не уничтожается, таймкод сохраняется, а само видео не нужно заново инициализировать или скачивать, когда пользователь возвращается, чтобы продолжить просмотр.
Это хороший пример того, как Activity сохраняет временное состояние DOM для частей интерфейса, которые становятся скрытыми, но с которыми пользователь, скорее всего, скоро снова будет взаимодействовать.
Пример показывает, что для некоторых тегов, таких как <video>, размонтирование и скрытие ведут себя по-разному. Если компонент рендерит DOM с побочным эффектом и вы хотите предотвратить этот побочный эффект, когда граница Activity его скрывает, добавьте эффект с возвращаемой функцией очистки.
Чаще всего это касается следующих тегов:
<video><audio><iframe>
Впрочем, большинство ваших компонентов React уже должны быть устойчивы к скрытию границей Activity. И концептуально о «скрытых» Activity стоит думать как о размонтированных.
Чтобы заранее обнаружить другие эффекты без правильной очистки — это важно не только для границ Activity, но и для многих других поведений React — мы рекомендуем использовать <StrictMode>.
У скрытых компонентов есть эффекты, которые не выполняются¶
Когда <Activity> «скрыт», все эффекты его дочерних элементов очищаются. Концептуально дочерние элементы размонтированы, но React сохраняет их состояние на потом. Это возможность Activity: подписки не будут активны для скрытых частей интерфейса, и работы для скрытого содержимого потребуется меньше.
Если вы полагаетесь на то, что монтирование эффекта очистит побочные эффекты компонента, переделайте эффект так, чтобы эта работа выполнялась в возвращаемой функции очистки.
Чтобы заранее находить проблемные эффекты, мы рекомендуем добавить <StrictMode>: он заранее выполняет размонтирования и монтирования Activity, чтобы поймать неожиданные побочные эффекты.
Источник — https://react.dev/reference/react/Activity