Beherrschen Sie React Suspense: Praktische Muster für effizienten Datenabruf, Ladezustände und robuste Fehlerbehandlung. Erstellen Sie flüssigere, widerstandsfähigere UIs.
React Suspense-Muster: Datenabruf und Error Boundaries
React Suspense ist eine leistungsstarke Funktion, die es Ihnen ermöglicht, die Rendering-Komponente "anzuhalten", während auf den Abschluss asynchroner Operationen, wie z.B. das Abrufen von Daten, gewartet wird. In Kombination mit Error Boundaries bietet es einen robusten Mechanismus zur Handhabung von Ladezuständen und Fehlern, was zu einer flüssigeren und widerstandsfähigeren Benutzererfahrung führt. Dieser Artikel untersucht verschiedene Muster, um Suspense und Error Boundaries effektiv in Ihren React-Anwendungen zu nutzen.
React Suspense verstehen
Im Kern ist Suspense ein Mechanismus, der es React ermöglicht, auf etwas zu warten, bevor eine Komponente gerendert wird. Dieses "Etwas" ist typischerweise eine asynchrone Operation, wie z.B. das Abrufen von Daten von einer API. Anstatt einen leeren Bildschirm oder einen potenziell irreführenden Zwischenzustand anzuzeigen, können Sie eine Fallback-Benutzeroberfläche (z.B. einen Ladespinner) anzeigen, während die Daten geladen werden.
Der Hauptvorteil ist eine verbesserte wahrgenommene Leistung und eine angenehmere Benutzererfahrung. Benutzern wird sofort visuelles Feedback gegeben, das anzeigt, dass etwas geschieht, anstatt sich zu fragen, ob die Anwendung eingefroren ist.
Schlüsselkonzepte
- Suspense-Komponente: Die
<Suspense>-Komponente umschließt Komponenten, die möglicherweise suspendieren. Sie akzeptiert einefallback-Prop, die die Benutzeroberfläche angibt, die gerendert werden soll, während die umschlossenen Komponenten suspendiert sind. - Fallback-Benutzeroberfläche: Dies ist die Benutzeroberfläche, die angezeigt wird, während die asynchrone Operation läuft. Es kann alles sein, von einem einfachen Ladespinner bis zu einer aufwendigeren Animation.
- Promise-Integration: Suspense funktioniert mit Promises. Wenn eine Komponente versucht, einen Wert aus einem Promise zu lesen, der noch nicht aufgelöst wurde, suspendiert React die Komponente und zeigt die Fallback-Benutzeroberfläche an.
- Datenquellen: Suspense basiert auf Datenquellen, die Suspense-fähig sind. Diese Quellen stellen eine API bereit, die es React ermöglicht, zu erkennen, wann Daten abgerufen werden.
Datenabruf mit Suspense
Um Suspense für den Datenabruf zu verwenden, benötigen Sie eine Suspense-fähige Datenabrufbibliothek. Hier ist ein gängiger Ansatz unter Verwendung einer benutzerdefinierten `fetchData`-Funktion:
Beispiel: Einfacher Datenabruf
Erstellen Sie zunächst eine Utility-Funktion zum Abrufen von Daten. Diese Funktion muss den 'Suspendierungs'-Aspekt behandeln. Wir werden unsere Fetch-Aufrufe in einer benutzerdefinierten Ressource umschließen, um den Promise-Zustand korrekt zu behandeln.
// 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;
Erstellen wir nun eine Komponente, die Suspense verwendet, um Benutzerdaten anzuzeigen:
// 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;
Umschließen Sie schließlich die UserProfile-Komponente mit <Suspense>:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
function App() {
return (
<Suspense fallback={<p>Loading user data...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
In diesem Beispiel versucht die UserProfile-Komponente, die user-Daten aus der resource zu lesen. Wenn die Daten noch nicht verfügbar sind (das Promise ist noch ausstehend), suspendiert die Komponente, und die Fallback-Benutzeroberfläche ("Loading user data...") wird angezeigt. Sobald die Daten abgerufen wurden, rendert die Komponente mit den tatsächlichen Benutzerinformationen neu.
Vorteile dieses Ansatzes
- Deklarativer Datenabruf: Die Komponente drückt aus, *welche* Daten sie benötigt, nicht *wie* sie diese abruft.
- Zentralisiertes Ladezustandsmanagement: Die Suspense-Komponente handhabt den Ladezustand und vereinfacht die Komponentenlogik.
Error Boundaries für Resilienz
Während Suspense Ladezustände elegant handhabt, behandelt es nicht unbedingt Fehler, die während des Datenabrufs oder des Komponenten-Renderings auftreten können. Hier kommen Error Boundaries ins Spiel.
Error Boundaries sind React-Komponenten, die JavaScript-Fehler an jeder Stelle in ihrem Kind-Komponentenbaum abfangen, diese Fehler protokollieren und eine Fallback-Benutzeroberfläche anzeigen, anstatt die gesamte Anwendung zum Absturz zu bringen. Sie sind entscheidend für den Aufbau widerstandsfähiger UIs, die unerwartete Fehler elegant handhaben können.
Eine Error Boundary erstellen
Um eine Error Boundary zu erstellen, müssen Sie eine Klassenkomponente definieren, die die Lebenszyklusmethoden static getDerivedStateFromError() und componentDidCatch() implementiert.
// 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) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error("Caught error: ", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div>
<h2>Something went wrong.</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;
Die Methode getDerivedStateFromError wird aufgerufen, wenn ein Fehler in einer Nachfolgerkomponente ausgelöst wird. Sie aktualisiert den Zustand, um anzuzeigen, dass ein Fehler aufgetreten ist.
Die Methode componentDidCatch wird aufgerufen, nachdem ein Fehler ausgelöst wurde. Sie empfängt den Fehler und die Fehlerinformationen, die Sie verwenden können, um den Fehler an einen Fehlerberichterstattungsdienst zu protokollieren oder eine informativere Fehlermeldung anzuzeigen.
Verwenden von Error Boundaries mit Suspense
Um Error Boundaries mit Suspense zu kombinieren, umschließen Sie einfach die <Suspense>-Komponente mit einer <ErrorBoundary>-Komponente:
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
import ErrorBoundary from './components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<p>Loading user data...</p>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}
export default App;
Tritt nun ein Fehler beim Datenabruf oder Rendering der UserProfile-Komponente auf, fängt die Error Boundary den Fehler ab und zeigt die Fallback-Benutzeroberfläche an, wodurch ein Absturz der gesamten Anwendung verhindert wird.
Fortgeschrittene Suspense-Muster
Über den grundlegenden Datenabruf und die Fehlerbehandlung hinaus bietet Suspense mehrere fortgeschrittene Muster für den Aufbau komplexerer UIs.
Code-Splitting mit Suspense
Code-Splitting ist der Prozess, Ihre Anwendung in kleinere Chunks aufzuteilen, die bei Bedarf geladen werden können. Dies kann die anfängliche Ladezeit Ihrer Anwendung erheblich verbessern.
React.lazy und Suspense machen Code-Splitting unglaublich einfach. Sie können React.lazy verwenden, um Komponenten dynamisch zu importieren, und sie dann mit <Suspense> umschließen, um eine Fallback-Benutzeroberfläche anzuzeigen, während die Komponenten geladen werden.
// components/MyComponent.js
import React from 'react';
const MyComponent = React.lazy(() => import('./AnotherComponent'));
function App() {
return (
<Suspense fallback={<p>Loading component...</p>}>
<MyComponent />
</Suspense>
);
}
export default App;
In diesem Beispiel wird die MyComponent bei Bedarf geladen. Während sie geladen wird, wird die Fallback-Benutzeroberfläche ("Loading component...") angezeigt. Sobald die Komponente geladen ist, wird sie normal gerendert.
Paralleler Datenabruf
Suspense ermöglicht es Ihnen, mehrere Datenquellen parallel abzurufen und eine einzige Fallback-Benutzeroberfläche anzuzeigen, während alle Daten geladen werden. Dies kann nützlich sein, wenn Sie Daten von mehreren APIs abrufen müssen, um eine einzelne Komponente zu rendern.
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>Posts:</h3>
<ul>
{posts.map(post => (<li key={post.id}>{post.title}</li>))}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback={<p>Loading user data and posts...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
In diesem Beispiel ruft die UserProfile-Komponente Benutzerdaten und Post-Daten parallel ab. Die <Suspense>-Komponente zeigt eine einzige Fallback-Benutzeroberfläche an, während beide Datenquellen geladen werden.
Transition API mit useTransition
React 18 führte den useTransition-Hook ein, der Suspense erweitert, indem er eine Möglichkeit bietet, UI-Updates als Übergänge zu verwalten. Dies bedeutet, dass Sie bestimmte Zustandsaktualisierungen als weniger dringend markieren und verhindern können, dass sie die Benutzeroberfläche blockieren. Dies ist besonders hilfreich beim Umgang mit langsameren Datenabrufen oder komplexen Rendering-Operationen, wodurch die wahrgenommene Leistung verbessert wird.
So können Sie useTransition verwenden:
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}>
Show User Profile
</button>
{isPending && <p>Loading...</p>}
<Suspense fallback={<p>Loading user data...</p>}>
{showProfile && <UserProfile />}
</Suspense>
</div>
);
}
export default App;
In diesem Beispiel initiiert das Klicken auf die Schaltfläche "Show User Profile" einen Übergang. startTransition markiert das setShowProfile-Update als Übergang, sodass React andere UI-Updates priorisieren kann. Der isPending-Wert von useTransition gibt an, ob ein Übergang im Gange ist, sodass Sie visuelles Feedback geben können (z.B. Deaktivieren der Schaltfläche und Anzeigen einer Ladeanzeige).
Best Practices für die Verwendung von Suspense und Error Boundaries
- Umschließen Sie Suspense um den kleinstmöglichen Bereich: Vermeiden Sie es, große Teile Ihrer Anwendung mit
<Suspense>zu umschließen. Umschließen Sie stattdessen nur die Komponenten, die tatsächlich suspendieren müssen. Dies minimiert die Auswirkungen auf den Rest der Benutzeroberfläche. - Verwenden Sie aussagekräftige Fallback-UIs: Die Fallback-UI sollte Benutzern klares und informatives Feedback darüber geben, was geschieht. Vermeiden Sie generische Ladespinner; versuchen Sie stattdessen, mehr Kontext bereitzustellen (z.B. "Benutzerdaten werden geladen...").
- Platzieren Sie Error Boundaries strategisch: Überlegen Sie genau, wo Sie Error Boundaries platzieren. Platzieren Sie sie hoch genug im Komponentenbaum, um Fehler abzufangen, die mehrere Komponenten betreffen könnten, aber niedrig genug, um das Abfangen von Fehlern zu vermeiden, die spezifisch für eine einzelne Komponente sind.
- Protokollieren Sie Fehler: Verwenden Sie die Methode
componentDidCatch, um Fehler an einen Fehlerberichterstattungsdienst zu protokollieren. Dies hilft Ihnen, Fehler in Ihrer Anwendung zu identifizieren und zu beheben. - Stellen Sie benutzerfreundliche Fehlermeldungen bereit: Die von Error Boundaries angezeigte Fallback-UI sollte Benutzern hilfreiche Informationen über den Fehler und mögliche Maßnahmen bereitstellen. Vermeiden Sie technische Fachsprache; verwenden Sie stattdessen eine klare und prägnante Sprache.
- Testen Sie Ihre Error Boundaries: Stellen Sie sicher, dass Ihre Error Boundaries ordnungsgemäß funktionieren, indem Sie absichtlich Fehler in Ihrer Anwendung auslösen.
Internationale Überlegungen
Bei der Verwendung von Suspense und Error Boundaries in internationalen Anwendungen sollten Sie Folgendes beachten:
- Lokalisierung: Stellen Sie sicher, dass die Fallback-UIs und Fehlermeldungen für jede von Ihrer Anwendung unterstützte Sprache ordnungsgemäß lokalisiert sind. Verwenden Sie Internationalisierungsbibliotheken (i18n) wie
react-intloderi18next, um Übersetzungen zu verwalten. - Rechts-nach-links (RTL) Layouts: Wenn Ihre Anwendung RTL-Sprachen (z.B. Arabisch, Hebräisch) unterstützt, stellen Sie sicher, dass die Fallback-UIs und Fehlermeldungen in RTL-Layouts korrekt angezeigt werden. Verwenden Sie CSS-Logik-Eigenschaften (z.B.
margin-inline-startanstelle vonmargin-left), um sowohl LTR- als auch RTL-Layouts zu unterstützen. - Barrierefreiheit: Stellen Sie sicher, dass die Fallback-UIs und Fehlermeldungen für Benutzer mit Behinderungen zugänglich sind. Verwenden Sie ARIA-Attribute, um semantische Informationen über den Ladezustand und die Fehlermeldungen bereitzustellen.
- Kulturelle Sensibilität: Achten Sie auf kulturelle Unterschiede beim Entwurf von Fallback-UIs und Fehlermeldungen. Vermeiden Sie die Verwendung von Bildern oder Sprache, die in bestimmten Kulturen beleidigend oder unangemessen sein könnten. Beispielsweise könnte ein gängiger Ladespinner in einigen Kulturen negativ wahrgenommen werden.
Beispiel: Lokalisierte Fehlermeldung
Mit react-intl können Sie lokalisierte Fehlermeldungen erstellen:
// components/ErrorBoundary.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
class ErrorBoundary extends React.Component {
// ... (same as before)
render() {
if (this.state.hasError) {
return (
<div>
<h2><FormattedMessage id="error.title" defaultMessage="Something went wrong." /></h2>
<p><FormattedMessage id="error.message" defaultMessage="Please try again later." /></p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Definieren Sie dann die Übersetzungen in Ihren Locale-Dateien:
// locales/en.json
{
"error.title": "Something went wrong.",
"error.message": "Please try again later."
}
// locales/fr.json
{
"error.title": "Quelque chose s'est mal passé.",
"error.message": "Veuillez réessayer plus tard."
}
Fazit
React Suspense und Error Boundaries sind unverzichtbare Werkzeuge für den Aufbau moderner, widerstandsfähiger und benutzerfreundlicher UIs. Durch das Verständnis und die Anwendung der in diesem Artikel beschriebenen Muster können Sie die wahrgenommene Leistung und die Gesamtqualität Ihrer React-Anwendungen erheblich verbessern. Denken Sie daran, Internationalisierung und Barrierefreiheit zu berücksichtigen, um sicherzustellen, dass Ihre Anwendungen von einem globalen Publikum genutzt werden können.
Asynchroner Datenabruf und die richtige Fehlerbehandlung sind kritische Aspekte jeder Webanwendung. Suspense, kombiniert mit Error Boundaries, bieten eine deklarative und effiziente Möglichkeit, diese Komplexitäten in React zu verwalten, was zu einer flüssigeren und zuverlässigeren Benutzererfahrung für Benutzer auf der ganzen Welt führt.