En dypdykk i Reacts useSyncExternalStore-hook for sømløs integrasjon med eksterne datakilder og tilstandshåndteringsbiblioteker. Lær å effektivt håndtere delt tilstand i React-applikasjoner.
React useSyncExternalStore: Mestring av ekstern tilstandsintegrasjon
Reacts useSyncExternalStore-hook, introdusert i React 18, tilbyr en kraftig og effektiv måte å integrere eksterne datakilder og tilstandshåndteringsbiblioteker i React-komponentene dine. Denne hooken lar komponenter abonnere på endringer i eksterne lagre, og sikrer at brukergrensesnittet alltid reflekterer de nyeste dataene, samtidig som ytelsen optimaliseres. Denne guiden gir en omfattende oversikt over useSyncExternalStore, som dekker dens kjernekonsepter, bruksmønstre og beste praksis.
Forstå behovet for useSyncExternalStore
I mange React-applikasjoner vil du støte på scenarier der tilstand må håndteres utenfor komponenttreet. Dette er ofte tilfellet når du arbeider med:
- Tredjepartsbiblioteker: Integrasjon med biblioteker som administrerer sin egen tilstand (f.eks. en databaseforbindelse, et nettleser-API eller en fysikkmotor).
- Delt tilstand på tvers av komponenter: Håndtering av tilstand som må deles mellom komponenter som ikke er direkte relatert (f.eks. brukerautentiseringsstatus, applikasjonsinnstillinger eller en global hendelsesbuss).
- Eksterne datakilder: Henting og visning av data fra eksterne API-er eller databaser.
Tradisjonelle tilstandshåndteringsløsninger som useState og useReducer er godt egnet for å håndtere lokal komponenttilstand. De er imidlertid ikke designet for å håndtere ekstern tilstand effektivt. Bruk av dem direkte med eksterne datakilder kan føre til ytelsesproblemer, inkonsekvente oppdateringer og kompleks kode.
useSyncExternalStore adresserer disse utfordringene ved å tilby en standardisert og optimalisert måte å abonnere på endringer i eksterne lagre. Det sikrer at komponenter kun gjengis på nytt når relevante data endres, noe som minimerer unødvendige oppdateringer og forbedrer den generelle ytelsen.
Kjernekonsepter for useSyncExternalStore
useSyncExternalStore tar tre argumenter:
subscribe: En funksjon som tar en tilbakekallingsfunksjon som argument og abonnerer på det eksterne lageret. Tilbakekallingsfunksjonen vil bli kalt når lagerets data endres.getSnapshot: En funksjon som returnerer et øyeblikksbilde av dataene fra det eksterne lageret. Denne funksjonen skal returnere en stabil verdi som React kan bruke til å avgjøre om dataene har endret seg. Den må være ren og rask.getServerSnapshot(valgfritt): En funksjon som returnerer startverdien til lageret under server-side rendering. Dette er avgjørende for å sikre at den første HTML-en samsvarer med klient-side rendering. Det brukes KUN i server-side rendering-miljøer. Hvis det utelates i et klientmiljø, brukesgetSnapshoti stedet. Det er viktig at denne verdien aldri endres etter at den opprinnelig ble gjengitt på serveren.
Her er en oversikt over hvert argument:
1. subscribe
subscribe-funksjonen er ansvarlig for å etablere en forbindelse mellom React-komponenten og det eksterne lageret. Den mottar en tilbakekallingsfunksjon, som den skal kalle hver gang lagerets data endres. Denne tilbakekallingsfunksjonen brukes typisk til å utløse en gjengivelse av komponenten.
Eksempel:
const subscribe = (callback) => {
store.addListener(callback);
return () => {
store.removeListener(callback);
};
};
I dette eksemplet legger store.addListener tilbakekallingsfunksjonen til lagerets lytterliste. Funksjonen returnerer en opprydningsfunksjon som fjerner lytteren når komponenten avmonteres, noe som forhindrer minnelekkasjer.
2. getSnapshot
getSnapshot-funksjonen er ansvarlig for å hente et øyeblikksbilde av dataene fra det eksterne lageret. Dette øyeblikksbildet skal være en stabil verdi som React kan bruke til å bestemme om dataene har endret seg. React bruker Object.is for å sammenligne det gjeldende øyeblikksbildet med det forrige øyeblikksbildet. Derfor må det være raskt, og det anbefales sterkt at det returnerer en primitiv verdi (streng, tall, boolsk, null eller udefinert).
Eksempel:
const getSnapshot = () => {
return store.getData();
};
I dette eksemplet returnerer store.getData de gjeldende dataene fra lageret. React vil sammenligne denne verdien med den forrige verdien for å avgjøre om komponenten trenger å gjengis på nytt.
3. getServerSnapshot (Valgfritt)
getServerSnapshot-funksjonen er bare relevant når server-side rendering (SSR) brukes. Denne funksjonen kalles under den første servergjengivelsen, og resultatet brukes som startverdien for lageret før hydrering skjer på klienten. Å returnere konsistente verdier er avgjørende for vellykket SSR.
Eksempel:
const getServerSnapshot = () => {
return store.getInitialDataForServer();
};
I dette eksemplet returnerer `store.getInitialDataForServer` startdataene som er passende for server-side rendering.
Grunnleggende bruks eksempel
La oss vurdere et enkelt eksempel der vi har et eksternt lager som administrerer en teller. Vi kan bruke useSyncExternalStore til å vise tellerverdien i en React-komponent:
// Eksternt lager
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React-komponent
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>Antall: {count}</p>
<button onClick={increment}>Øk</button>
</div>>
);
}
export default Counter;
I dette eksemplet oppretter createStore et enkelt eksternt lager som administrerer en tellerverdi. Counter-komponenten bruker useSyncExternalStore til å abonnere på endringer i lageret og vise det gjeldende antallet. Når inkrementerknappen klikkes, oppdaterer setState-funksjonen lagerets verdi, noe som utløser en gjengivelse av komponenten.
Integrasjon med tilstandshåndteringsbiblioteker
useSyncExternalStore er spesielt nyttig for å integrere med tilstandshåndteringsbiblioteker som Zustand, Jotai og Recoil. Disse bibliotekene tilbyr sine egne mekanismer for tilstandshåndtering, og useSyncExternalStore lar deg sømløst integrere dem i React-komponentene dine.
Her er et eksempel på integrasjon med Zustand:
import { useStore } from 'zustand';
import { create } from 'zustand';
// Zustand lager
const useBoundStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// React-komponent
function Counter() {
const count = useStore(useBoundStore, (state) => state.count);
const increment = useStore(useBoundStore, (state) => state.increment);
return (
<div>
<p>Antall: {count}</p>
<button onClick={increment}>Øk</button>
</div>>
);
}
export default Counter;
Zustand forenkler lageropprettelsen. Dets interne subscribe og getSnapshot implementeringer brukes implisitt når du abonnerer på en bestemt tilstand.
Her er et eksempel på integrasjon med Jotai:
import { atom, useAtom } from 'jotai'
// Jotai atom
const countAtom = atom(0)
// React-komponent
function Counter() {
const [count, setCount] = useAtom(countAtom)
return (
<div>
<p>Antall: {count}</p>
<button onClick={() => setCount(count + 1)}>Øk</button>
</div>>
)
}
export default Counter;
Jotai bruker atomer for å håndtere tilstand. useAtom håndterer internt abonnement og øyeblikksbilder.
Ytelsesoptimalisering
useSyncExternalStore tilbyr flere mekanismer for ytelsesoptimalisering:
- Selektive oppdateringer: React gjengir kun komponenten på nytt når verdien returnert av
getSnapshotendres. Dette sikrer at unødvendige gjengivelser unngås. - Batching av oppdateringer: React samler oppdateringer fra flere eksterne lagre til en enkelt gjengivelse. Dette reduserer antall gjengivelser og forbedrer den generelle ytelsen.
- Unngå utdaterte lukninger:
useSyncExternalStoresikrer at komponenten alltid har tilgang til de nyeste dataene fra det eksterne lageret, selv når man håndterer asynkrone oppdateringer.
For ytterligere ytelsesoptimalisering, vurder følgende beste praksis:
- Minimer mengden data returnert av
getSnapshot: Returner kun dataene som faktisk er nødvendige for komponenten. Dette reduserer mengden data som må sammenlignes og forbedrer effektiviteten av oppdateringsprosessen. - Bruk memo-teknikker: Memo-iser resultatene av dyre beregninger eller datatransformasjoner. Dette kan forhindre unødvendige re-beregninger og forbedre ytelsen.
- Unngå unødvendige abonnement: Abonner kun på det eksterne lageret når komponenten faktisk er synlig. Dette kan redusere antall aktive abonnement og forbedre den generelle ytelsen.
- Sørg for at
getSnapshotreturnerer et nytt *stabilt* objekt kun hvis dataene har endret seg: Unngå å lage nye objekter/tabeller/funksjoner hvis de underliggende dataene faktisk ikke har endret seg. Returner samme objekt ved referanse hvis mulig.
Server-Side Rendering (SSR) med useSyncExternalStore
Når du bruker useSyncExternalStore med server-side rendering (SSR), er det avgjørende å oppgi en getServerSnapshot-funksjon. Denne funksjonen sikrer at den opprinnelige HTML-en som gjengis på serveren, samsvarer med klient-side rendering, og forhindrer hydreringsfeil og forbedrer brukeropplevelsen.
Her er et eksempel på bruk av getServerSnapshot:
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const getServerSnapshot = () => initialValue; // Viktig for SSR
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
getServerSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React-komponent
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot, counterStore.getServerSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>Antall: {count}</p>
<button onClick={increment}>Øk</button>
</div>>
);
}
export default Counter;
I dette eksemplet returnerer getServerSnapshot tellerenes startverdi. Dette sikrer at den opprinnelige HTML-en som gjengis på serveren, samsvarer med klient-side rendering. `getServerSnapshot` bør returnere en stabil og forutsigbar verdi. Den bør også utføre samme logikk som getSnapshot-funksjonen på serveren. Unngå å aksessere nettleserspesifikke API-er eller globale variabler i getServerSnapshot.
Avanserte bruksmønstre
useSyncExternalStore kan brukes i en rekke avanserte scenarier, inkludert:
- Integrasjon med nettleser-API-er: Abonnement på endringer i nettleser-API-er som
localStorageellernavigator.onLine. - Oppretting av egendefinerte hooks: Innkapsling av logikken for å abonnere på et eksternt lager i en egendefinert hook.
- Bruk med Context API: Kombinering av
useSyncExternalStoremed React Context API for å tilby delt tilstand til et komponenttre.
La oss se på et eksempel på oppretting av en egendefinert hook for abonnement på localStorage:
import { useSyncExternalStore } from 'react';
function useLocalStorage(key, initialValue) {
const getSnapshot = () => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error("Feil ved henting av verdi fra localStorage:", error);
return initialValue;
}
};
const subscribe = (callback) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
const setItem = (value) => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
window.dispatchEvent(new Event('storage')); // Utløs lagringshendelse manuelt for oppdateringer på samme side
} catch (error) {
console.error("Feil ved innstilling av verdi i localStorage:", error);
}
};
const serverSnapshot = () => initialValue;
const storedValue = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
return [storedValue, setItem];
}
export default useLocalStorage;
I dette eksemplet er useLocalStorage en egendefinert hook som abonnerer på endringer i localStorage. Den bruker useSyncExternalStore til å administrere abonnementet og hente den gjeldende verdien fra localStorage. Den utløser også korrekt en lagringshendelse for å sikre at oppdateringer på samme side reflekteres (da `storage`-hendelser bare utløses i andre faner). serverSnapshot sikrer at startverdier gis korrekt i servermiljøer.
Beste praksis og vanlige fallgruver
Her er noen beste praksis og vanlige fallgruver å unngå når du bruker useSyncExternalStore:
- Unngå å mutere det eksterne lageret direkte: Bruk alltid lagerets API til å oppdatere dataene. Direkte mutasjon av lageret kan føre til inkonsekvente oppdateringer og uventet oppførsel.
- Sørg for at
getSnapshoter ren og rask:getSnapshotskal ikke ha noen bivirkninger og skal raskt returnere en stabil verdi. Dyre beregninger eller datatransformasjoner bør memo-iseres. - Oppgi en
getServerSnapshot-funksjon ved bruk av SSR: Dette er avgjørende for å sikre at den opprinnelige HTML-en som gjengis på serveren, samsvarer med klient-side rendering. - Håndter feil på en grasiøs måte: Bruk try-catch-blokker for å håndtere potensielle feil når du får tilgang til det eksterne lageret.
- Rydd opp abonnement: Avslutt alltid abonnementet på det eksterne lageret når komponenten avmonteres for å forhindre minnelekkasjer.
subscribe-funksjonen skal returnere en opprydningsfunksjon som fjerner lytteren. - Forstå ytelsesimplikasjonene: Selv om
useSyncExternalStoreer optimalisert for ytelse, er det viktig å forstå de potensielle konsekvensene av å abonnere på eksterne lagre. Minimer mengden data returnert avgetSnapshotog unngå unødvendige abonnement. - Test grundig: Sørg for at integrasjon med lageret fungerer korrekt i forskjellige scenarier, spesielt i server-side rendering og samtidskjøring.
Konklusjon
useSyncExternalStore er en kraftig og effektiv hook for å integrere eksterne datakilder og tilstandshåndteringsbiblioteker i React-komponentene dine. Ved å forstå dens kjernekonsepter, bruksmønstre og beste praksis, kan du effektivt administrere delt tilstand i React-applikasjonene dine og optimalisere ytelsen. Enten du integrerer med tredjepartsbiblioteker, administrerer delt tilstand på tvers av komponenter eller henter data fra eksterne API-er, gir useSyncExternalStore en standardisert og pålitelig løsning. Omfavn den for å bygge mer robuste, vedlikeholdbare og ytelsesdyktige React-applikasjoner for et globalt publikum.
Videre ressurser
- <a href="https://react.dev/reference/react/useSyncExternalStore">React-dokumentasjon om useSyncExternalStore</a>
- <a href="https://github.com/facebook/react/pull/23346">React 18 Pull Request som introduserer useSyncExternalStore</a>
- <a href="https://zustand.js.org/">Zustand State Management Library</a>
- <a href="https://jotai.org/">Jotai State Management Library</a>