Kompleksowy przewodnik po hooku useSyncExternalStore w React, omawiaj膮cy jego cel, implementacj臋, korzy艣ci i zaawansowane przypadki u偶ycia do zarz膮dzania stanem zewn臋trznym.
React useSyncExternalStore: Opanowanie synchronizacji stanu zewn臋trznego
useSyncExternalStore
to hook Reacta wprowadzony w wersji 18, kt贸ry pozwala na subskrybowanie i odczytywanie danych z zewn臋trznych 藕r贸de艂 w spos贸b kompatybilny z renderowaniem wsp贸艂bie偶nym. Ten hook wype艂nia luk臋 mi臋dzy stanem zarz膮dzanym przez React a stanem zewn臋trznym, takim jak dane z bibliotek firm trzecich, API przegl膮darki czy innych framework贸w UI. Przyjrzyjmy si臋 bli偶ej jego celowi, implementacji i korzy艣ciom.
Zrozumienie potrzeby u偶ycia useSyncExternalStore
Wbudowane mechanizmy zarz膮dzania stanem w React (useState
, useReducer
, Context API) dzia艂aj膮 wyj膮tkowo dobrze dla danych 艣ci艣le powi膮zanych z drzewem komponent贸w React. Jednak wiele aplikacji musi integrowa膰 si臋 ze 藕r贸d艂ami danych *poza* kontrol膮 Reacta. Te zewn臋trzne 藕r贸d艂a mog膮 obejmowa膰:
- Biblioteki do zarz膮dzania stanem firm trzecich: Integracja z bibliotekami takimi jak Zustand, Jotai czy Valtio.
- API przegl膮darki: Dost臋p do danych z
localStorage
,IndexedDB
czy Network Information API. - Dane pobierane z serwer贸w: Chocia偶 cz臋sto preferowane s膮 biblioteki takie jak React Query i SWR, czasami mo偶esz chcie膰 mie膰 bezpo艣redni膮 kontrol臋.
- Inne frameworki UI: W aplikacjach hybrydowych, gdzie React wsp贸艂istnieje z innymi technologiami UI.
Bezpo艣rednie odczytywanie i zapisywanie do tych zewn臋trznych 藕r贸de艂 wewn膮trz komponentu React mo偶e prowadzi膰 do problem贸w, szczeg贸lnie przy renderowaniu wsp贸艂bie偶nym. React mo偶e wyrenderowa膰 komponent z nieaktualnymi danymi, je艣li zewn臋trzne 藕r贸d艂o zmieni si臋 w trakcie, gdy React przygotowuje nowy ekran. useSyncExternalStore
rozwi膮zuje ten problem, dostarczaj膮c mechanizm, dzi臋ki kt贸remu React mo偶e bezpiecznie synchronizowa膰 si臋 ze stanem zewn臋trznym.
Jak dzia艂a useSyncExternalStore
Hook useSyncExternalStore
przyjmuje trzy argumenty:
subscribe
: Funkcja, kt贸ra akceptuje callback. Ten callback zostanie wywo艂any za ka偶dym razem, gdy zmieni si臋 zewn臋trzny magazyn. Funkcja powinna zwr贸ci膰 inn膮 funkcj臋, kt贸ra po wywo艂aniu anuluje subskrypcj臋 zewn臋trznego magazynu.getSnapshot
: Funkcja, kt贸ra zwraca aktualn膮 warto艣膰 zewn臋trznego magazynu. React u偶ywa tej funkcji do odczytania warto艣ci magazynu podczas renderowania.getServerSnapshot
(opcjonalny): Funkcja, kt贸ra zwraca pocz膮tkow膮 warto艣膰 zewn臋trznego magazynu na serwerze. Jest to konieczne tylko w przypadku renderowania po stronie serwera (SSR). Je艣li nie zostanie podana, React u偶yjegetSnapshot
na serwerze.
Hook zwraca aktualn膮 warto艣膰 zewn臋trznego magazynu, uzyskan膮 z funkcji getSnapshot
. React zapewnia, 偶e komponent zostanie ponownie wyrenderowany za ka偶dym razem, gdy zmieni si臋 warto艣膰 zwr贸cona przez getSnapshot
, co jest okre艣lane za pomoc膮 por贸wnania Object.is
.
Podstawowy przyk艂ad: Synchronizacja z localStorage
Stw贸rzmy prosty przyk艂ad, kt贸ry u偶ywa useSyncExternalStore
do synchronizacji warto艣ci z localStorage
.
Value from localStorage: {localValue}
W tym przyk艂adzie:
subscribe
: Nas艂uchuje na zdarzeniestorage
w obiekciewindow
. To zdarzenie jest wywo艂ywane za ka偶dym razem, gdylocalStorage
jest modyfikowany przez inn膮 kart臋 lub okno.getSnapshot
: Pobiera warto艣膰myValue
zlocalStorage
.getServerSnapshot
: Zwraca domy艣ln膮 warto艣膰 na potrzeby renderowania po stronie serwera. Warto艣膰 ta mog艂aby by膰 pobrana z pliku cookie, je艣li u偶ytkownik wcze艣niej ustawi艂 warto艣膰.MyComponent
: U偶ywauseSyncExternalStore
do subskrybowania zmian wlocalStorage
i wy艣wietlania bie偶膮cej warto艣ci.
Zaawansowane przypadki u偶ycia i uwagi
1. Integracja z bibliotekami do zarz膮dzania stanem firm trzecich
useSyncExternalStore
sprawdza si臋 doskonale podczas integracji komponent贸w React z zewn臋trznymi bibliotekami do zarz膮dzania stanem. Sp贸jrzmy na przyk艂ad z u偶yciem Zustand:
Count: {count}
W tym przyk艂adzie useSyncExternalStore
jest u偶ywany do subskrybowania zmian w magazynie Zustand. Zauwa偶, jak przekazujemy useStore.subscribe
i useStore.getState
bezpo艣rednio do hooka, co sprawia, 偶e integracja jest bezproblemowa.
2. Optymalizacja wydajno艣ci za pomoc膮 memoizacji
Poniewa偶 getSnapshot
jest wywo艂ywany przy ka偶dym renderowaniu, kluczowe jest zapewnienie jego wydajno艣ci. Unikaj kosztownych oblicze艅 wewn膮trz getSnapshot
. W razie potrzeby zmemoizuj wynik getSnapshot
za pomoc膮 useMemo
lub podobnych technik.
Rozwa偶 ten (potencjalnie problematyczny) przyk艂ad:
```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} ))}
W tym przyk艂adzie getSnapshot
(funkcja inline przekazana jako drugi argument do useSyncExternalStore
) wykonuje kosztown膮 operacj臋 map
na du偶ej tablicy. Ta operacja b臋dzie wykonywana przy *ka偶dym* renderowaniu, nawet je艣li bazowe dane si臋 薪械 zmieni艂y. Aby to zoptymalizowa膰, mo偶emy zmemoizowa膰 wynik:
-
{data.slice(0, 10).map((item) => (
- {item} ))}
Teraz operacja map
jest wykonywana tylko wtedy, gdy zmienia si臋 externalStore.getState()
. Uwaga: w rzeczywisto艣ci trzeba b臋dzie przeprowadzi膰 g艂臋bokie por贸wnanie `externalStore.getState()` lub u偶y膰 innej strategii, je艣li magazyn mutuje ten sam obiekt. Przyk艂ad zosta艂 uproszczony dla cel贸w demonstracyjnych.
3. Obs艂uga renderowania wsp贸艂bie偶nego
G艂贸wn膮 zalet膮 useSyncExternalStore
jest jego kompatybilno艣膰 z funkcjami renderowania wsp贸艂bie偶nego w React. Renderowanie wsp贸艂bie偶ne pozwala Reactowi przygotowywa膰 wiele wersji interfejsu u偶ytkownika jednocze艣nie. Gdy zewn臋trzny magazyn zmienia si臋 podczas renderowania wsp贸艂bie偶nego, useSyncExternalStore
zapewnia, 偶e React zawsze u偶yje najaktualniejszych danych podczas zatwierdzania zmian w DOM.
Bez useSyncExternalStore
komponenty mog艂yby renderowa膰 si臋 z nieaktualnymi danymi, co prowadzi艂oby do niesp贸jno艣ci wizualnych i nieoczekiwanego zachowania. Metoda getSnapshot
w useSyncExternalStore
zosta艂a zaprojektowana tak, aby by膰 synchroniczna i szybka, co pozwala Reactowi szybko okre艣li膰, czy zewn臋trzny magazyn zmieni艂 si臋 podczas renderowania.
4. Uwagi dotycz膮ce renderowania po stronie serwera (SSR)
Podczas u偶ywania useSyncExternalStore
z renderowaniem po stronie serwera, kluczowe jest dostarczenie funkcji getServerSnapshot
. Ta funkcja jest u偶ywana do pobrania pocz膮tkowej warto艣ci zewn臋trznego magazynu na serwerze. Bez niej React spr贸buje u偶y膰 getSnapshot
na serwerze, co mo偶e nie by膰 mo偶liwe, je艣li zewn臋trzny magazyn opiera si臋 na API specyficznych dla przegl膮darki (np. localStorage
).
Funkcja getServerSnapshot
powinna zwraca膰 warto艣膰 domy艣ln膮 lub pobiera膰 dane ze 藕r贸d艂a po stronie serwera (np. pliki cookie, baza danych). Zapewnia to, 偶e pocz膮tkowy kod HTML wyrenderowany na serwerze zawiera poprawne dane.
5. Obs艂uga b艂臋d贸w
Solidna obs艂uga b艂臋d贸w jest kluczowa, zw艂aszcza gdy mamy do czynienia z zewn臋trznymi 藕r贸d艂ami danych. Opakuj funkcje getSnapshot
i getServerSnapshot
w bloki try...catch
, aby obs艂u偶y膰 potencjalne b艂臋dy. Odpowiednio loguj b艂臋dy i dostarczaj warto艣ci zapasowe, aby zapobiec awarii aplikacji.
6. Niestandardowe hooki w celu ponownego u偶ycia
Aby promowa膰 ponowne u偶ycie kodu, zamknij logik臋 useSyncExternalStore
w niestandardowym hooku. U艂atwia to wsp贸艂dzielenie logiki mi臋dzy wieloma komponentami.
Na przyk艂ad, stw贸rzmy niestandardowy hook do uzyskiwania dost臋pu do okre艣lonego klucza w localStorage
:
Teraz mo偶esz 艂atwo u偶y膰 tego hooka w dowolnym komponencie:
```javascript import useLocalStorage from './useLocalStorage'; function MyComponent() { const [name, setName] = useLocalStorage('userName', 'Guest'); return (Hello, {name}!
setName(e.target.value)} />Dobre praktyki
- Dbaj o szybko艣膰
getSnapshot
: Unikaj kosztownych oblicze艅 wewn膮trz funkcjigetSnapshot
. W razie potrzeby zmemoizuj wynik. - Dostarcz
getServerSnapshot
dla SSR: Upewnij si臋, 偶e pocz膮tkowy kod HTML wyrenderowany na serwerze zawiera poprawne dane. - U偶ywaj niestandardowych hook贸w: Zamykaj logik臋
useSyncExternalStore
w niestandardowych hookach, aby zapewni膰 lepsz膮 reu偶ywalno艣膰 i 艂atwo艣膰 w utrzymaniu. - Obs艂uguj b艂臋dy z gracj膮: Opakuj
getSnapshot
igetServerSnapshot
w blokitry...catch
. - Minimalizuj subskrypcje: Subskrybuj tylko te cz臋艣ci zewn臋trznego magazynu, kt贸rych komponent faktycznie potrzebuje. Zmniejsza to liczb臋 niepotrzebnych ponownych renderowa艅.
- Rozwa偶 alternatywy: Oce艅, czy
useSyncExternalStore
jest naprawd臋 konieczny. W prostszych przypadkach inne techniki zarz膮dzania stanem mog膮 by膰 bardziej odpowiednie.
Alternatywy dla useSyncExternalStore
Chocia偶 useSyncExternalStore
jest pot臋偶nym narz臋dziem, nie zawsze jest najlepszym rozwi膮zaniem. Rozwa偶 te alternatywy:
- Wbudowane zarz膮dzanie stanem (
useState
,useReducer
, Context API): Je艣li dane s膮 艣ci艣le powi膮zane z drzewem komponent贸w React, te wbudowane opcje s膮 cz臋sto wystarczaj膮ce. - React Query/SWR: Do pobierania danych, te biblioteki zapewniaj膮 doskona艂e mo偶liwo艣ci buforowania, uniewa偶niania i obs艂ugi b艂臋d贸w.
- Zustand/Jotai/Valtio: Te minimalistyczne biblioteki do zarz膮dzania stanem oferuj膮 prosty i wydajny spos贸b zarz膮dzania stanem aplikacji.
- Redux/MobX: W przypadku z艂o偶onych aplikacji z globalnym stanem, Redux lub MobX mog膮 by膰 lepszym wyborem (chocia偶 wprowadzaj膮 wi臋cej kodu standardowego).
Wyb贸r zale偶y od konkretnych wymaga艅 Twojej aplikacji.
Podsumowanie
useSyncExternalStore
to cenne uzupe艂nienie zestawu narz臋dzi Reacta, umo偶liwiaj膮ce p艂ynn膮 integracj臋 z zewn臋trznymi 藕r贸d艂ami stanu przy jednoczesnym zachowaniu kompatybilno艣ci z renderowaniem wsp贸艂bie偶nym. Rozumiej膮c jego cel, implementacj臋 i zaawansowane przypadki u偶ycia, mo偶esz wykorzysta膰 ten hook do budowania solidnych i wydajnych aplikacji React, kt贸re skutecznie wsp贸艂dzia艂aj膮 z danymi z r贸偶nych 藕r贸de艂.
Pami臋taj, aby priorytetowo traktowa膰 wydajno艣膰, z gracj膮 obs艂ugiwa膰 b艂臋dy i rozwa偶y膰 alternatywne rozwi膮zania, zanim si臋gniesz po useSyncExternalStore
. Dzi臋ki starannemu planowaniu i implementacji, ten hook mo偶e znacznie zwi臋kszy膰 elastyczno艣膰 i moc Twoich aplikacji React.
Dalsza lektura
- Dokumentacja React dla useSyncExternalStore
- Przyk艂ady z r贸偶nymi bibliotekami do zarz膮dzania stanem (Zustand, Jotai, Valtio)
- Testy wydajno艣ci por贸wnuj膮ce
useSyncExternalStore
z innymi podej艣ciami