Den kommande lanseringen av React 18 innehåller en rad förbättringar, inklusive de efterlängtade ”concurrent features” (se den här artikeln ). En sådan funktion är det nya startTransition-API:et, som gör det möjligt att ange olika prioritet för tillståndsuppdateringar:
Brådskande uppdateringar sker som svar på användarens agerande – som när de klickar eller scrollar – och säkerställer att applikationens UI förblir responsivt.
Icke-brådskande uppdateringar låter React rendera en ny vy “i bakgrunden”, som därefter sömlöst ersätter befintlig UI så fort renderingen färdigställts.
I ett scenario där en användare utför en åtgärd som resulterar i en tillståndsuppdatering och en (potentiellt dyr) omrendering av en vy, kommer renderingsprocessen - i den nuvarande versionen av React - att blockera alla andra aktiviteter på sidan och eventuell fortsatt användarinteraktion tills processen är slutförd.
Med startTransition, flyttas omrenderingen av en vy till bakgrunden, vilket innebär att användaren fortfarande kan interagera ostört med andra UI-element medan övergången till den uppdaterade vyn förbereds.
I den här översikten ges en detaljerad motivation för startTransition-funktionen, och i den tillhörande demon går man igenom hur funktionen kan användas för att undvika långsam rendering samt bevara ett responsivt UI.
I den här artikeln fokuserar man på ett annat scenario där startTransition kan förbättra användarupplevelsen, närmare bestämt datahämtning.
Om Suspense
I React 18 återfinns även den så kallade Suspense-funktionen, en mekanism som gör det möjligt att ”skjuta upp” en rendering av en komponent medan den hämtar beroende resurser.
Se listing 1 och 2 för ett exempel på hur Suspense kan användas.
Alla listreferenser hänvisar till exempelapplikationen som finns på Github
// Listing 1.
// in App.js.
<Suspense fallback={`Loading repos...`}>
<Repos endpoint={`${BASE_URL}/${currentUser}/repos`} /> </Suspense>
Som en del av renderingen av en profilvy för en Github-användare ansvarar en komponent vid namn Repos för att hämta och rendera data användarens repositories. Komponenten wrappas i en så kallad Suspense boundary med en fallback som visas medan data hämtas.
// Listing 2.
// in Repos.js.
export const Repos = ({ endpoint }) => {
const {
data
} = useSWR(endpoint, fetchResource, { suspense: true });
return (
<>
<h2>Repositories</h2>
{data.map((repo, i) => <p key={i}>{repo.name}</p>)}
</>
);
}
När renderingen av Repos inleds skickas en begäran om att hämta dess resurs, d.v.s. användarens repositories. Första gången som useSWR -hooken anropas kommer den att pausa renderingen genom att kasta ett Promise-objekt som fångas upp av den övergripande Suspense boundaryn. När Promise-objektet är löst, kommer React att fortsätta rendera komponenten; useSWR kallas på nytt och den här gången är data tillgängliga och returneras till att användas i den resulterande markupen. Detta flöde illustreras i figur 1.
useSWR -hooken är en del av ett 3-parts datahämtningsbibliotek som kallas för SWR och som har stöd för Suspense.
Märk att Repos -komponenten inte hanterar några laddningstillstånd; denna aspekt sköts helt av den överordnade Suspense boundaryn.
Datahämtning med startTransition och Suspense
Vad spelar startTransition för roll i det här scenariot med datahämtning? Se listing 3:
// Listing 3.
// in App.js.
const [
currentUser,
setCurrentUser
] = useState(null);
const [
showProfile,
setShowProfile
] = useState(false);
const [isPending, startTransition] = useTransition();
const selectUser = user => {
setCurrentUser(user);
startTransition(() => {
setShowProfile(true);
});
};
I UI:et som renderas av huvudkomponenten AppInternal finns två tillstånd:
currentUser : Den valda Github-användaren.
showProfile : Anger om profilvyn visas för Github-användaren eller inte.
UI:et kan antingen visa en lista över Github-användare (standardvy) eller en profilvy.
När en Github-användare väljs sker följande:
En brådskande tillståndsuppdatering görs genom ett anrop till setCurrentUser .
En icke-brådskande tillståndsuppdatering görs genom ett anrop till startTransition .
Genom att uppdatera showProfile -tillståndet inom StartTransition, kommer React att påbörja omrenderingen av komponenten AppInternal i bakgrunden, vilket i sin tur inleder renderingen av komponenterna UserProfile , Repos och Followers tillsammans med resurserna som vardera komponenten hämtar. När React har avslutat uppdateringen i bakgrunden, övergår standardvyn till profilvyn.
När currentUser -tillståndet uppdateras omedelbart (brådskande), kan standardvyn dessutom visa en diskret laddningsindikator intill den valda Github-användaren i listan medan åtgärden pågår:
// Listing 4.
// in App.js.
// the useTransition hook returns an isPending flag that is true while React is performing background work.
const [isPending, startTransition] = useTransition();
// ...
{user} {(isPending && currentUser === user) && '...'}
I följande video visas standardvyn och övergången till en profilvy när en Github-användare är vald; lägg märke till att standardvyn fortfarande är synlig medan profilvyn renderas i bakgrunden.
UI:et gör det även möjligt att välja ytterligare en Github-användare samtidigt som profilvyn förbereds för den som för närvarande är vald! Som nämnts i översikten, ”kommer React att kasta bort det gamla renderingsarbetet som inte slutfördes, och endast rendera den senaste uppdateringen”.
Hantering av laddningssekvenser
Som man kan se i demon, laddar och renderar profilvyn sina komponenter enligt följande sekvens:
Github-användarens information (namn och URL till profilbild) laddas först och renderas ”tillsammans” som en enhet.
Repositories och följare visas så fort motsvarande data kommer in; renderingsordningen av Repos och Followers är obestämd.
Tänk på att även om data om repositories och följare skulle anlända tidigare (d.v.s. snabbare) än användarinformationen, ser laddningssekvensen till att profilvyn inte visas förrän alla data är tillgängliga.
Men om det finns ett behov av att modifiera sekvensen, så att repositories och följare nu ska renderas samtidigt?
Detta kan vara svårt att göra i den nuvarande versionen av React, men i React 18 och med Suspense är ändringen enkel att göra, från:
// Listing 5.
// in App.js.
// separate Suspense boundaries allow for loading repos and followers "lazily", i.e. ensuring that rendering the
// critical UserProfile component is not delayed by the not-as-vital Repos and Followers components. */}
<Suspense fallback={`Loading repos...`}>
<Repos endpoint={`${BASE_URL}/${currentUser}/repos`} /> </Suspense>
<Suspense fallback={`Loading followers...`}>
<Followers endpoint={`${BASE_URL}/${currentUser}/followers`} /> </Suspense>
till:
// Listing 6.
// in App.js.
// by moving Repos and Followers under a common parent Suspense boundary, React guarantees that it will await
// _both_ components to finish fetching resources before resuming rendering them.
<Suspense fallback={`Loading repos and followers...`}>
<Repos endpoint={`${BASE_URL}/${currentUser}/repos`} />
<Followers endpoint={`${BASE_URL}/${currentUser}/followers`} /> </Suspense>
Men om React hade kunnat vänta på att allt Github-användarinnehåll laddas innan det går över till profilvyn då? Eftersom AppInternal -komponenten i sig är wrappad i en Suspense boundary, kan man helt enkelt ta bort alla inre Suspense boundaries, som t.ex. för Repos och Followers . Fallbacken Loading view... kommer dock inte att visas och störa standardvyn, eftersom användningen av startTransition inte triggar den överordnade Suspense boundaryn.
Implementation
I exempelkoden används SWR-datahämtningsbiblioteket, eftersom det har inbyggt stöd för Suspense.
Vissa resurser – som exempelvis bilder, som är statiska resurser – kan behöva en skräddarsydd approach för att vara kompatibla med Suspense. Vid rendering av användarinformation hämtas Github-data först och därefter görs ett anrop för att explicit ladda profilbilden:
// Listing 7.
//
// in UserProfile.js.
// start fetching the user information, which contains the avatar image URL.
// This call will suspend until the Github data becomes available.
const { data } = useSWR(endpoint, fetchResource, { suspense: true });
const {
name,
avatar_url
} = data;
// load (and let the browser cache) the avatar image; this call will also suspend rendering until the image has been
// loaded.
//
// It's necessary to explicitly control the fetching of this static asset, as the Github user's information and avatar are supposed to be rendered jointly.
const image = useImageFetch(avatar_url);
Se src/util.js för en anpassad implementation (för bilder i det här fallet) av datahämtning med stöd för Suspense.
Slutsats
I den här artikeln demonstreras det nya React 18 startTransition API:et för ett typiskt scenario med datahämtning; det bör nämnas att den beskrivna approachen ännu inte är fullt redo att kunna användas i produktion. Det krävs fortfarande en del arbete från React-teamet innan Suspense för datahämtning är fullt fungerande.
Inom en snar framtid kommer React att innehålla sina egna bibliotek för läsning och skrivande av data (se t.ex. följande artikel: react-fetch) kompletterat av inbyggd caching-funktionalitet . Initiativet med Server components kommer att ytterligare förbättra bästa praxis för datahämtning i React.
Kontakt
Marc Klefter | marc.klefter@edument.se
Comments