Детальний посібник з хука React useSyncExternalStore, що розглядає його призначення, реалізацію, переваги та розширені сценарії використання для керування зовнішнім станом.
React useSyncExternalStore: Досконале володіння синхронізацією зовнішнього стану
useSyncExternalStore
— це хук, представлений у React 18, який дозволяє підписуватися на зовнішні джерела даних та зчитувати їх у спосіб, сумісний із конкурентним рендерингом. Цей хук долає розрив між керованим станом React та зовнішнім станом, таким як дані зі сторонніх бібліотек, API браузера чи інших UI-фреймворків. Давайте глибше розберемося в його призначенні, реалізації та перевагах.
Розуміння потреби в useSyncExternalStore
Вбудовані засоби керування станом у React (useState
, useReducer
, Context API) чудово працюють для даних, тісно пов'язаних із деревом компонентів React. Однак багато застосунків потребують інтеграції з джерелами даних, що знаходяться *поза* контролем React. До таких зовнішніх джерел належать:
- Сторонні бібліотеки для керування станом: Інтеграція з бібліотеками, такими як Zustand, Jotai або Valtio.
- API браузера: Доступ до даних з
localStorage
,IndexedDB
або Network Information API. - Дані, отримані з серверів: Хоча часто перевага надається таким бібліотекам, як React Query та SWR, іноді може знадобитися прямий контроль.
- Інші UI-фреймворки: У гібридних застосунках, де React співіснує з іншими технологіями інтерфейсу.
Пряме зчитування та запис у ці зовнішні джерела в компоненті React може призвести до проблем, особливо при конкурентному рендерингу. React може відрендерити компонент із застарілими даними, якщо зовнішнє джерело зміниться, поки React готує новий екран. useSyncExternalStore
вирішує цю проблему, надаючи механізм для безпечної синхронізації React із зовнішнім станом.
Як працює useSyncExternalStore
Хук useSyncExternalStore
приймає три аргументи:
subscribe
: Функція, що приймає callback. Цей callback викликатиметься щоразу, коли зовнішнє сховище змінюється. Функція повинна повертати іншу функцію, яка при виклику скасовує підписку на зовнішнє сховище.getSnapshot
: Функція, що повертає поточне значення зовнішнього сховища. React використовує цю функцію для зчитування значення сховища під час рендерингу.getServerSnapshot
(необов'язково): Функція, що повертає початкове значення зовнішнього сховища на сервері. Це необхідно лише для рендерингу на стороні сервера (SSR). Якщо її не надати, React використовуватимеgetSnapshot
на сервері.
Хук повертає поточне значення зовнішнього сховища, отримане з функції getSnapshot
. React гарантує, що компонент буде повторно відрендерений щоразу, коли значення, повернуте getSnapshot
, змінюється, що визначається за допомогою порівняння Object.is
.
Базовий приклад: Синхронізація з localStorage
Створимо простий приклад, який використовує useSyncExternalStore
для синхронізації значення з localStorage
.
Value from localStorage: {localValue}
У цьому прикладі:
subscribe
: Прослуховує подіюstorage
на об'єктіwindow
. Ця подія спрацьовує щоразу, колиlocalStorage
змінюється іншою вкладкою або вікном.getSnapshot
: Отримує значенняmyValue
зlocalStorage
.getServerSnapshot
: Повертає значення за замовчуванням для рендерингу на стороні сервера. Це значення можна було б отримати з cookie, якби користувач раніше його встановив.MyComponent
: ВикористовуєuseSyncExternalStore
для підписки на зміни вlocalStorage
та відображення поточного значення.
Розширені сценарії використання та аспекти
1. Інтеграція зі сторонніми бібліотеками керування станом
useSyncExternalStore
чудово проявляє себе при інтеграції компонентів React зі сторонніми бібліотеками керування станом. Розглянемо приклад з використанням Zustand:
Count: {count}
У цьому прикладі useSyncExternalStore
використовується для підписки на зміни у сховищі Zustand. Зверніть увагу, як ми передаємо useStore.subscribe
та useStore.getState
безпосередньо в хук, що робить інтеграцію безшовною.
2. Оптимізація продуктивності за допомогою мемоізації
Оскільки getSnapshot
викликається при кожному рендері, вкрай важливо забезпечити його продуктивність. Уникайте дорогих обчислень усередині getSnapshot
. За потреби мемоізуйте результат getSnapshot
за допомогою useMemo
або подібних технік.
Розглянемо цей (потенційно проблемний) приклад:
```javascript import { useSyncExternalStore, useMemo } from 'react'; const externalStore = { data: [...Array(10000).keys()], // Large array listeners: [], subscribe(listener) { this.listeners.push(listener); return () => { this.listeners = this.listeners.filter((l) => l !== listener); }; }, setState(newData) { this.data = newData; this.listeners.forEach((listener) => listener()); }, getState() { return this.data; }, }; function ExpensiveComponent() { const data = useSyncExternalStore( externalStore.subscribe, () => externalStore.getState().map(x => x * 2) // Expensive operation ); return (-
{data.slice(0, 10).map((item) => (
- {item} ))}
У цьому прикладі getSnapshot
(вбудована функція, передана як другий аргумент в useSyncExternalStore
) виконує дорогу операцію map
над великим масивом. Ця операція буде виконуватися при *кожному* рендері, навіть якщо базові дані не змінилися. Щоб оптимізувати це, ми можемо мемоізувати результат:
-
{data.slice(0, 10).map((item) => (
- {item} ))}
Тепер операція map
виконується лише тоді, коли змінюється externalStore.getState()
. Примітка: вам насправді потрібно буде робити глибоке порівняння externalStore.getState()
або використовувати іншу стратегію, якщо сховище мутує той самий об'єкт. Приклад спрощено для демонстрації.
3. Робота з конкурентним рендерингом
Основна перевага useSyncExternalStore
— це його сумісність із функціями конкурентного рендерингу React. Конкурентний рендеринг дозволяє React готувати кілька версій UI одночасно. Коли зовнішнє сховище змінюється під час конкурентного рендерингу, useSyncExternalStore
гарантує, що React завжди використовуватиме найактуальніші дані при застосуванні змін до DOM.
Без useSyncExternalStore
компоненти могли б рендеритися із застарілими даними, що призводило б до візуальних невідповідностей та несподіваної поведінки. Метод getSnapshot
хука useSyncExternalStore
розроблений бути синхронним і швидким, що дозволяє React швидко визначати, чи змінилося зовнішнє сховище під час рендерингу.
4. Аспекти рендерингу на стороні сервера (SSR)
При використанні useSyncExternalStore
з рендерингом на стороні сервера важливо надавати функцію getServerSnapshot
. Ця функція використовується для отримання початкового значення зовнішнього сховища на сервері. Без неї React спробує використати getSnapshot
на сервері, що може бути неможливим, якщо зовнішнє сховище залежить від специфічних для браузера API (наприклад, localStorage
).
Функція getServerSnapshot
повинна повертати значення за замовчуванням або отримувати дані з серверного джерела (наприклад, файлів cookie, бази даних). Це гарантує, що початковий HTML, відрендерений на сервері, містить правильні дані.
5. Обробка помилок
Надійна обробка помилок є вкрай важливою, особливо при роботі із зовнішніми джерелами даних. Оберніть функції getSnapshot
та getServerSnapshot
у блоки try...catch
для обробки потенційних помилок. Логуйте помилки належним чином і надавайте резервні значення, щоб запобігти збоям застосунку.
6. Власні хуки для повторного використання
Для сприяння повторному використанню коду інкапсулюйте логіку useSyncExternalStore
у власний хук. Це полегшує спільне використання логіки між кількома компонентами.
Наприклад, давайте створимо власний хук для доступу до певного ключа в localStorage
:
Тепер ви можете легко використовувати цей хук у будь-якому компоненті:
```javascript import useLocalStorage from './useLocalStorage'; function MyComponent() { const [name, setName] = useLocalStorage('userName', 'Guest'); return (Hello, {name}!
setName(e.target.value)} />Найкращі практики
- Зберігайте
getSnapshot
швидким: Уникайте дорогих обчислень у функціїgetSnapshot
. За потреби мемоізуйте результат. - Надавайте
getServerSnapshot
для SSR: Переконайтеся, що початковий HTML, відрендерений на сервері, містить правильні дані. - Використовуйте власні хуки: Інкапсулюйте логіку
useSyncExternalStore
у власні хуки для кращого повторного використання та підтримки. - Витончено обробляйте помилки: Оберніть
getSnapshot
таgetServerSnapshot
у блокиtry...catch
. - Мінімізуйте підписки: Підписуйтесь лише на ті частини зовнішнього сховища, які дійсно потрібні компоненту. Це зменшує кількість непотрібних повторних рендерів.
- Розглядайте альтернативи: Оцініть, чи справді необхідний
useSyncExternalStore
. Для простих випадків інші методи керування станом можуть бути більш доречними.
Альтернативи useSyncExternalStore
Хоча useSyncExternalStore
є потужним інструментом, він не завжди є найкращим рішенням. Розгляньте ці альтернативи:
- Вбудоване керування станом (
useState
,useReducer
, Context API): Якщо дані тісно пов'язані з деревом компонентів React, цих вбудованих опцій часто буває достатньо. - React Query/SWR: Для отримання даних ці бібліотеки надають чудові можливості кешування, інвалідації та обробки помилок.
- Zustand/Jotai/Valtio: Ці мінімалістичні бібліотеки керування станом пропонують простий та ефективний спосіб керування станом застосунку.
- Redux/MobX: Для складних застосунків із глобальним станом Redux або MobX можуть бути кращим вибором (хоча вони додають більше шаблонного коду).
Вибір залежить від конкретних вимог вашого застосунку.
Висновок
useSyncExternalStore
— це цінне доповнення до інструментарію React, що забезпечує безшовну інтеграцію із зовнішніми джерелами стану, зберігаючи при цьому сумісність із конкурентним рендерингом. Розуміючи його призначення, реалізацію та розширені сценарії використання, ви можете використовувати цей хук для створення надійних і продуктивних застосунків React, які ефективно взаємодіють з даними з різних джерел.
Не забувайте надавати пріоритет продуктивності, витончено обробляти помилки та розглядати альтернативні рішення, перш ніж звертатися до useSyncExternalStore
. Завдяки ретельному плануванню та реалізації цей хук може значно підвищити гнучкість і потужність ваших застосунків React.
Подальше вивчення
- Документація React для useSyncExternalStore
- Приклади з різними бібліотеками керування станом (Zustand, Jotai, Valtio)
- Тести продуктивності, що порівнюють
useSyncExternalStore
з іншими підходами