useSyncExternalStore¶
useSyncExternalStore
- это хук React, позволяющий подписаться на внешнее хранилище.
1 2 3 4 5 |
|
Описание¶
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
¶
Вызовите useSyncExternalStore
на верхнем уровне вашего компонента для чтения значения из внешнего хранилища данных.
1 2 3 4 5 6 7 8 9 10 |
|
Она возвращает моментальный снимок данных в хранилище. В качестве аргументов необходимо передать две функции:
- Функция
subscribe
должна подписываться на стор и возвращать функцию, которая отписывается. - Функция
getSnapshot
должна считывать моментальный снимок данных из хранилища.
Параметры¶
-
subscribe
: Функция, принимающая один аргументcallback
и подписывающаяся на стор. Когда стор изменяется, он должен вызвать предоставленныйcallback
. Это приведет к повторному отображению компонента. Функцияsubscribe
должна возвращать функцию, которая очищает подписку. -
getSnapshot
: Функция, которая возвращает снимок данных в хранилище, необходимых компоненту. Пока хранилище не изменилось, повторные вызовыgetSnapshot
должны возвращать одно и то же значение. Если стор изменился, а возвращаемое значение отличается (по сравнению сObject.is
), React перерисовывает компонент. -
опциональная
getServerSnapshot
: Функция, возвращающая начальный снимок данных в хранилище. Он будет использоваться только при рендеринге сервера и при гидратации рендеримого сервером контента на клиенте. Серверный снимок должен быть одинаковым для клиента и сервера, и обычно сериализуется и передается от сервера к клиенту. Если вы опустите этот аргумент, рендеринг компонента на сервере приведет к ошибке.
Возвращает¶
Текущий снимок хранилища, который вы можете использовать в своей логике рендеринга.
Ограничения¶
-
Снимок хранилища, возвращаемый
getSnapshot
, должен быть неизменяемым. Если базовое хранилище имеет изменяемые данные, верните новый неизменяемый снимок, если данные изменились. В противном случае возвращается кэшированный последний снимок. -
Если во время повторного рендеринга передается другая функция
subscribe
, React повторно подпишется на стор, используя новую переданную функциюsubscribe
. Вы можете предотвратить это, объявивsubscribe
вне компонента. -
Если хранилище будет изменено во время неблокируемого обновления перехода, React вернется к выполнению этого обновления как блокирующего. В частности, при каждом обновлении перехода React будет вызывать
getSnapshot
второй раз непосредственно перед применением изменений к DOM. Если он вернет другое значение, чем при первоначальном вызове, React перезапустит обновление с нуля, на этот раз применяя его как блокирующее обновление, чтобы гарантировать, что каждый компонент на экране отражает одну и ту же версию хранилища. -
Не рекомендуется приостанавливать рендеринг на основе значения хранилища, возвращаемого
useSyncExternalStore
. Причина в том, что мутации во внешнем хранилище не могут быть помечены как неблокирующие обновления переходов, поэтому они будут вызывать ближайший фолбекSuspense
, заменяя уже отрендеренный контент на экране загрузочным спиннером, что, как правило, создает плохой UX.Например, не рекомендуется использовать следующее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const LazyProductDetailPage = lazy( () => import('./ProductDetailPage.js') ); function ShoppingApp() { const selectedProductId = useSyncExternalStore(...); // ❌ Calling `use` with a Promise dependent on `selectedProductId` const data = use(fetchItem(selectedProductId)) // ❌ Conditionally rendering a lazy component based // on `selectedProductId` return selectedProductId != null ? <LazyProductDetailPage /> : <FeaturedProducts />; }
Использование¶
Подписка на внешний стор¶
Большинство ваших компонентов React будут читать данные только из своих пропсов, состояния и контекста. Однако иногда компоненту необходимо читать данные из какого-то хранилища вне React, которое меняется со временем. К ним относятся:
- Сторонние библиотеки управления состоянием, которые хранят состояние вне React.
- Браузерные API, которые предоставляют изменяемое значение и события для подписки на его изменения.
Вызовите useSyncExternalStore
на верхнем уровне вашего компонента, чтобы прочитать значение из внешнего хранилища данных.
1 2 3 4 5 6 7 8 9 10 |
|
Она возвращает моментальный снимок данных в хранилище. Вам нужно передать две функции в качестве аргументов:
- Функция
subscribe
должна подписываться на стор и возвращать функцию, которая отписывается. getSnapshot
функция должна прочитать снимок данных из стора.
React будет использовать эти функции, чтобы сохранить ваш компонент подписанным на стор и перерисовывать его при изменениях.
Например, в песочнице ниже, todosStore
реализован как внешний стор, который хранит данные вне React. Компонент TodosApp
подключается к этому внешнему хранилищу с помощью хука useSyncExternalStore
.
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 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Встроенное состояние
Когда это возможно, мы рекомендуем использовать встроенное состояние React с помощью useState
и useReducer
. API useSyncExternalStore
в основном полезен, если вам нужно интегрироваться с существующим не-React кодом.
Подписка на API браузера¶
Еще одна причина добавить useSyncExternalStore
- это когда вы хотите подписаться на какое-то значение, предоставляемое браузером, которое меняется со временем. Например, предположим, что вы хотите, чтобы ваш компонент отображал, активно ли сетевое соединение. Браузер предоставляет эту информацию через свойство navigator.onLine
.
Это значение может меняться без ведома React, поэтому вы должны считывать его с помощью useSyncExternalStore
.
1 2 3 4 5 6 7 8 9 |
|
Чтобы реализовать функцию getSnapshot
, прочитайте текущее значение из API браузера:
1 2 3 |
|
Далее необходимо реализовать функцию subscribe
. Например, при изменении navigator.onLine
браузер запускает события online
и offline
на объекте window
. Вам нужно подписать аргумент callback
на соответствующие события, а затем вернуть функцию, которая очистит подписки:
1 2 3 4 5 6 7 8 |
|
Теперь React знает, как прочитать значение из внешнего API navigator.onLine
и как подписаться на его изменения. Отключите устройство от сети и обратите внимание на то, что в ответ на это компонент снова отображается:
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 |
|
Извлечение логики в пользовательский хук¶
Обычно вы не будете писать useSyncExternalStore
непосредственно в своих компонентах. Вместо этого вы, как правило, вызываете его из своего собственного пользовательского хука. Это позволяет вам использовать одно и то же внешнее хранилище в разных компонентах.
Например, этот пользовательский хук useOnlineStatus
отслеживает, находится ли сеть в режиме онлайн:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Теперь различные компоненты могут вызывать useOnlineStatus
без повторения базовой реализации:
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 12 13 14 15 16 17 18 19 20 21 22 |
|
Добавление поддержки серверного рендеринга¶
Если ваше приложение React использует серверный рендеринг, ваши компоненты React также будут запускаться вне среды браузера для генерации начального HTML. Это создает несколько проблем при подключении к внешнему стору:
- Если вы подключаетесь к API только для браузера, он не будет работать, поскольку не существует на сервере.
- Если вы подключаетесь к стороннему хранилищу данных, вам потребуется, чтобы его данные совпадали на сервере и клиенте.
Чтобы решить эти проблемы, передайте функцию getServerSnapshot
в качестве третьего аргумента в useSyncExternalStore
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Функция getServerSnapshot
похожа на getSnapshot
, но запускается только в двух ситуациях:
- Она запускается на сервере при генерации HTML.
- Она запускается на клиенте во время hydration, т. е. когда React берет серверный HTML и делает его интерактивным.
Это позволяет указать начальное значение snapshot, которое будет использоваться до того, как приложение станет интерактивным. Если нет значимого начального значения для серверного рендеринга, опустите этот аргумент для принудительного рендеринга на клиенте.
Убедитесь, что getServerSnapshot
возвращает те же данные при первоначальном рендеринге на клиенте, что и на сервере. Например, если getServerSnapshot
вернул некоторое предварительно заполненное содержимое стора на сервере, вам нужно передать это содержимое клиенту. Один из способов сделать это - выдать тег <script>
во время рендеринга сервера, который устанавливает глобал типа window.MY_STORE_DATA
, и читать из этого глобала на клиенте в getServerSnapshot
. Ваш внешний стор должен предоставить инструкции о том, как это сделать.
Устранение неполадок¶
Я получаю ошибку: "The result of getSnapshot
should be cached"¶
Эта ошибка означает, что ваша функция getSnapshot
возвращает новый объект при каждом вызове, например:
1 2 3 4 5 6 |
|
React будет перерисовывать компонент, если возвращаемое значение getSnapshot
отличается от предыдущего. Вот почему, если вы всегда возвращаете другое значение, вы попадете в бесконечный цикл и получите эту ошибку.
Ваш объект getSnapshot
должен возвращать другой объект только в том случае, если что-то действительно изменилось. Если ваше хранилище содержит неизменяемые данные, вы можете возвращать эти данные напрямую:
1 2 3 4 |
|
Если данные вашего стора изменчивы, ваша функция getSnapshot
должна возвращать неизменяемый снимок. Это означает, что ей нужно создавать новые объекты, но она не должна делать это при каждом вызове. Вместо этого она должна хранить последний вычисленный снимок и возвращать тот же снимок, что и в прошлый раз, если данные в хранилище не изменились. Как определить, изменились ли изменяемые данные, зависит от вашего хранилища изменяемых данных.
Моя функция subscribe
вызывается после каждого рендеринга¶
Эта функция subscribe
определена внутри компонента, поэтому при каждом повторном рендере она будет другой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
React повторно подпишется на ваш стор, если вы передадите другую функцию subscribe
между повторными рендерами. Если это вызывает проблемы с производительностью и вы хотите избежать повторной подписки, переместите функцию subscribe
наружу:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Как вариант, оберните subscribe
в useCallback
, чтобы повторно подписываться только при изменении какого-либо аргумента:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Источник — https://react.dev/reference/react/useSyncExternalStore