BemÀstra React Suspense med praktiska mönster för effektiv datahÀmtning, laddningslÀgen och robust felhantering. Bygg smidigare, mer motstÄndskraftiga anvÀndarupplevelser.
Mönster för React Suspense: DatahÀmtning och felgrÀnser
React Suspense Àr en kraftfull funktion som lÄter dig "pausa" (suspend) renderingen av en komponent medan du vÀntar pÄ att asynkrona operationer, sÄsom datahÀmtning, ska slutföras. Tillsammans med felgrÀnser (Error Boundaries) erbjuder det en robust mekanism för att hantera laddningslÀgen och fel, vilket resulterar i en smidigare och mer motstÄndskraftig anvÀndarupplevelse. Denna artikel utforskar olika mönster för att effektivt utnyttja Suspense och felgrÀnser i dina React-applikationer.
FörstÄ React Suspense
I grund och botten Àr Suspense en mekanism som lÄter React vÀnta pÄ nÄgot innan en komponent renderas. Detta "nÄgot" Àr vanligtvis en asynkron operation, som att hÀmta data frÄn ett API. IstÀllet för att visa en tom skÀrm eller ett potentiellt vilseledande mellantillstÄnd kan du visa ett fallback-grÀnssnitt (t.ex. en laddningsspinner) medan datan laddas.
Den största fördelen Àr förbÀttrad upplevd prestanda och en trevligare anvÀndarupplevelse. AnvÀndare fÄr omedelbart visuell feedback som indikerar att nÄgot hÀnder, istÀllet för att undra om applikationen har fryst.
Nyckelkoncept
- Suspense-komponent: Komponenten
<Suspense>omsluter komponenter som kan pausas. Den accepterar enfallback-prop, som specificerar det grÀnssnitt som ska renderas medan de omslutna komponenterna Àr pausade. - Fallback-grÀnssnitt: Detta Àr det grÀnssnitt som visas medan den asynkrona operationen pÄgÄr. Det kan vara allt frÄn en enkel laddningsspinner till en mer avancerad animation.
- Promise-integration: Suspense fungerar med Promises. NÀr en komponent försöker lÀsa ett vÀrde frÄn ett Promise som Ànnu inte har lösts, pausar React komponenten och visar fallback-grÀnssnittet.
- DatakÀllor: Suspense förlitar sig pÄ datakÀllor som Àr medvetna om Suspense. Dessa kÀllor exponerar ett API som lÄter React upptÀcka nÀr data hÀmtas.
HĂ€mta data med Suspense
För att anvÀnda Suspense för datahÀmtning behöver du ett datahÀmtningsbibliotek som Àr medvetet om Suspense. HÀr Àr ett vanligt tillvÀgagÄngssÀtt med en anpassad `fetchData`-funktion:
Exempel: Enkel datahÀmtning
Skapa först en hjÀlpfunktion för att hÀmta data. Denna funktion mÄste hantera "pausnings"-aspekten. Vi kommer att omsluta vÄra fetch-anrop i en anpassad resurs för att korrekt hantera promisetillstÄndet.
// utils/api.js
const wrapPromise = (promise) => {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
}
return result;
},
};
};
const fetchData = (url) => {
const promise = fetch(url)
.then((res) => res.json())
.then((data) => data);
return wrapPromise(promise);
};
export default fetchData;
LÄt oss nu skapa en komponent som anvÀnder Suspense för att visa anvÀndardata:
// components/UserProfile.js
import React from 'react';
import fetchData from '../utils/api';
const resource = fetchData('https://jsonplaceholder.typicode.com/users/1');
function UserProfile() {
const user = resource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
);
}
export default UserProfile;
Slutligen, omslut komponenten UserProfile med <Suspense>:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
function App() {
return (
<Suspense fallback={<p>Laddar anvÀndardata...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
I det hÀr exemplet försöker komponenten UserProfile lÀsa user-datan frÄn resource. Om datan Ànnu inte Àr tillgÀnglig (Promiset Àr fortfarande vÀntande), pausas komponenten och fallback-grÀnssnittet ("Laddar anvÀndardata...") visas. NÀr datan har hÀmtats renderas komponenten om med den faktiska anvÀndarinformationen.
Fördelar med detta tillvÀgagÄngssÀtt
- Deklarativ datahÀmtning: Komponenten uttrycker *vilken* data den behöver, inte *hur* den ska hÀmtas.
- Centraliserad hantering av laddningslÀge: Suspense-komponenten hanterar laddningslÀget, vilket förenklar komponentlogiken.
FelgrÀnser för motstÄndskraft
Medan Suspense hanterar laddningslÀgen pÄ ett smidigt sÀtt, hanterar det inte i sig fel som kan uppstÄ under datahÀmtning eller komponentrendering. Det Àr hÀr felgrÀnser (Error Boundaries) kommer in.
FelgrÀnser Àr React-komponenter som fÄngar JavaScript-fel var som helst i sitt underordnade komponenttrÀd, loggar dessa fel och visar ett fallback-grÀnssnitt istÀllet för att hela applikationen kraschar. De Àr avgörande för att bygga motstÄndskraftiga grÀnssnitt som kan hantera ovÀntade fel pÄ ett elegant sÀtt.
Skapa en felgrÀns
För att skapa en felgrÀns mÄste du definiera en klasskomponent som implementerar livscykelmetoderna static getDerivedStateFromError() och componentDidCatch().
// components/ErrorBoundary.js
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
// Uppdatera state sÄ att nÀsta rendering visar fallback-grÀnssnittet.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, errorInfo) {
// Du kan ocksÄ logga felet till en felrapporteringstjÀnst
console.error("Caught error: ", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// Du kan rendera vilket anpassat fallback-grÀnssnitt som helst
return (
<div>
<h2>NÄgot gick fel.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Metoden getDerivedStateFromError anropas nÀr ett fel kastas i en underordnad komponent. Den uppdaterar state för att indikera att ett fel har intrÀffat.
Metoden componentDidCatch anropas efter att ett fel har kastats. Den tar emot felet och felinformationen, som du kan anvÀnda för att logga felet till en felrapporteringstjÀnst eller visa ett mer informativt felmeddelande.
AnvÀnda felgrÀnser med Suspense
För att kombinera felgrÀnser med Suspense, omslut helt enkelt komponenten <Suspense> med en <ErrorBoundary>-komponent:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
import ErrorBoundary from './components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<p>Laddar anvÀndardata...</p>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}
export default App;
Om ett fel nu uppstÄr under datahÀmtning eller rendering av komponenten UserProfile kommer felgrÀnsen att fÄnga felet och visa fallback-grÀnssnittet, vilket förhindrar att hela applikationen kraschar.
Avancerade Suspense-mönster
Utöver grundlÀggande datahÀmtning och felhantering erbjuder Suspense flera avancerade mönster för att bygga mer sofistikerade grÀnssnitt.
Koddelning med Suspense
Koddelning (code splitting) Àr processen att dela upp din applikation i mindre delar (chunks) som kan laddas vid behov. Detta kan avsevÀrt förbÀttra den initiala laddningstiden för din applikation.
React.lazy och Suspense gör koddelning otroligt enkelt. Du kan anvÀnda React.lazy för att dynamiskt importera komponenter och sedan omsluta dem med <Suspense> för att visa ett fallback-grÀnssnitt medan komponenterna laddas.
// components/MyComponent.js
import React from 'react';
const MyComponent = React.lazy(() => import('./AnotherComponent'));
function App() {
return (
<Suspense fallback={<p>Laddar komponent...</p>}>
<MyComponent />
</Suspense>
);
}
export default App;
I det hÀr exemplet laddas MyComponent vid behov. Medan den laddas visas fallback-grÀnssnittet ("Laddar komponent..."). NÀr komponenten Àr laddad renderas den som vanligt.
Parallell datahÀmtning
Suspense lÄter dig hÀmta data frÄn flera kÀllor parallellt och visa ett enda fallback-grÀnssnitt medan all data laddas. Detta kan vara anvÀndbart nÀr du behöver hÀmta data frÄn flera API:er för att rendera en enda komponent.
import React, { Suspense } from 'react';
import fetchData from './api';
const userResource = fetchData('https://jsonplaceholder.typicode.com/users/1');
const postsResource = fetchData('https://jsonplaceholder.typicode.com/posts?userId=1');
function UserProfile() {
const user = userResource.read();
const posts = postsResource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<h3>InlÀgg:</h3>
<ul>
{posts.map(post => (<li key={post.id}>{post.title}</li>))}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback={<p>Laddar anvÀndardata och inlÀgg...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
I det hÀr exemplet hÀmtar komponenten UserProfile bÄde anvÀndardata och inlÀggsdata parallellt. Komponenten <Suspense> visar ett enda fallback-grÀnssnitt medan bÄda datakÀllorna laddas.
Transition API med useTransition
React 18 introducerade hooken useTransition, som förbÀttrar Suspense genom att erbjuda ett sÀtt att hantera UI-uppdateringar som övergÄngar (transitions). Detta innebÀr att du kan markera vissa state-uppdateringar som mindre brÄdskande och förhindra att de blockerar grÀnssnittet. Detta Àr sÀrskilt anvÀndbart nÀr man hanterar lÄngsammare datahÀmtning eller komplexa renderingsoperationer, vilket förbÀttrar den upplevda prestandan.
SÄ hÀr kan du anvÀnda useTransition:
import React, { useState, Suspense, useTransition } from 'react';
import fetchData from './api';
const resource = fetchData('https://jsonplaceholder.typicode.com/users/1');
function UserProfile() {
const user = resource.read();
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
);
}
function App() {
const [isPending, startTransition] = useTransition();
const [showProfile, setShowProfile] = useState(false);
const handleClick = () => {
startTransition(() => {
setShowProfile(true);
});
};
return (
<div>
<button onClick={handleClick} disabled={isPending}>
Visa anvÀndarprofil
</button>
{isPending && <p>Laddar...</p>}
<Suspense fallback={<p>Laddar anvÀndardata...</p>}>
{showProfile && <UserProfile />}
</Suspense>
</div>
);
}
export default App;
I det hÀr exemplet initierar ett klick pÄ knappen "Visa anvÀndarprofil" en övergÄng. startTransition markerar setShowProfile-uppdateringen som en övergÄng, vilket lÄter React prioritera andra UI-uppdateringar. VÀrdet isPending frÄn useTransition indikerar om en övergÄng pÄgÄr, vilket gör att du kan ge visuell feedback (t.ex. inaktivera knappen och visa ett laddningsmeddelande).
BÀsta praxis för att anvÀnda Suspense och felgrÀnser
- Omslut minsta möjliga omrÄde med Suspense: Undvik att omsluta stora delar av din applikation med
<Suspense>. Omslut istÀllet bara de komponenter som faktiskt behöver pausas. Detta minimerar pÄverkan pÄ resten av grÀnssnittet. - AnvÀnd meningsfulla fallback-grÀnssnitt: Fallback-grÀnssnittet bör ge anvÀndarna tydlig och informativ feedback om vad som hÀnder. Undvik generiska laddningsspinners; försök istÀllet att ge mer sammanhang (t.ex. "Laddar anvÀndardata...").
- Placera felgrÀnser strategiskt: TÀnk noga igenom var du ska placera felgrÀnser. Placera dem tillrÀckligt högt upp i komponenttrÀdet för att fÄnga fel som kan pÄverka flera komponenter, men tillrÀckligt lÄgt för att undvika att fÄnga fel som Àr specifika för en enskild komponent.
- Logga fel: AnvÀnd
componentDidCatch-metoden för att logga fel till en felrapporteringstjÀnst. Detta hjÀlper dig att identifiera och ÄtgÀrda fel i din applikation. - Ge anvÀndarvÀnliga felmeddelanden: Fallback-grÀnssnittet som visas av felgrÀnser bör ge anvÀndarna anvÀndbar information om felet och vad de kan göra Ät det. Undvik teknisk jargong; anvÀnd istÀllet ett tydligt och koncist sprÄk.
- Testa dina felgrÀnser: Se till att dina felgrÀnser fungerar korrekt genom att medvetet kasta fel i din applikation.
Internationella övervÀganden
NÀr du anvÀnder Suspense och felgrÀnser i internationella applikationer, tÀnk pÄ följande:
- Lokalisering: Se till att fallback-grÀnssnitten och felmeddelandena Àr korrekt lokaliserade för varje sprÄk som din applikation stöder. AnvÀnd internationaliseringsbibliotek (i18n) som
react-intlelleri18nextför att hantera översÀttningar. - Höger-till-vÀnster (RTL) layouter: Om din applikation stöder RTL-sprÄk (t.ex. arabiska, hebreiska), se till att fallback-grÀnssnitten och felmeddelandena visas korrekt i RTL-layouter. AnvÀnd logiska CSS-egenskaper (t.ex.
margin-inline-startistÀllet förmargin-left) för att stödja bÄde LTR- och RTL-layouter. - TillgÀnglighet: Se till att fallback-grÀnssnitten och felmeddelandena Àr tillgÀngliga för anvÀndare med funktionsnedsÀttningar. AnvÀnd ARIA-attribut för att ge semantisk information om laddningslÀget och felmeddelanden.
- Kulturell hÀnsyn: Var medveten om kulturella skillnader nÀr du utformar fallback-grÀnssnitt och felmeddelanden. Undvik att anvÀnda bilder eller sprÄk som kan vara stötande eller olÀmpliga i vissa kulturer. Till exempel kan en vanlig laddningsspinner uppfattas negativt i vissa kulturer.
Exempel: Lokaliserat felmeddelande
Med react-intl kan du skapa lokaliserade felmeddelanden:
// components/ErrorBoundary.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
class ErrorBoundary extends React.Component {
// ... (samma som tidigare)
render() {
if (this.state.hasError) {
return (
<div>
<h2><FormattedMessage id="error.title" defaultMessage="NÄgot gick fel." /></h2>
<p><FormattedMessage id="error.message" defaultMessage="VÀnligen försök igen senare." /></p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Definiera sedan översÀttningarna i dina lokaliseringsfiler:
// locales/en.json
{
"error.title": "Something went wrong.",
"error.message": "Please try again later."
}
// locales/sv.json
{
"error.title": "NÄgot gick fel.",
"error.message": "VÀnligen försök igen senare."
}
Sammanfattning
React Suspense och felgrÀnser Àr vÀsentliga verktyg för att bygga moderna, motstÄndskraftiga och anvÀndarvÀnliga grÀnssnitt. Genom att förstÄ och tillÀmpa de mönster som beskrivs i den hÀr artikeln kan du avsevÀrt förbÀttra den upplevda prestandan och den övergripande kvaliteten pÄ dina React-applikationer. Kom ihÄg att ta hÀnsyn till internationalisering och tillgÀnglighet för att sÀkerstÀlla att dina applikationer Àr anvÀndbara för en global publik.
Asynkron datahÀmtning och korrekt felhantering Àr kritiska aspekter av alla webbapplikationer. Suspense, i kombination med felgrÀnser, erbjuder ett deklarativt och effektivt sÀtt att hantera dessa komplexiteter i React, vilket resulterar i en smidigare och mer pÄlitlig anvÀndarupplevelse för anvÀndare över hela vÀrlden.