Die meisten heute erstellten Webanwendungen empfangen Daten von einer API. Wenn Sie diese Daten abrufen, müssen wir bestimmte Situationen berücksichtigen, in denen die Daten möglicherweise noch nicht empfangen wurden. Vielleicht war es eine unterbrochene Verbindung. Vielleicht wurde der Endpunkt geändert. Wer weiß. Was auch immer das Problem ist, der Endnutzer erhält am Ende nichts auf der Benutzeroberfläche.
Also sollten wir dem Rechnung tragen!
Die übliche Methode zur Handhabung dieses Problems ist die Verwendung eines Zustands wie `isLoading` in der Anwendung. Der Wert von `isLoading` hängt von den Daten ab, die wir empfangen möchten. Zum Beispiel könnte es ein einfaches Boolesches sein, bei dem ein zurückgegebenes `true` (was bedeutet, dass wir noch auf die Daten warten), wir einen Lade-Spinner anzeigen, um anzuzeigen, dass die Anwendung arbeitet. Andernfalls zeigen wir die Daten an.

📷 Kredit: Jian Wei
Während dies nicht gänzlich schlecht ist, haben die großartigen Leute, die an React arbeiten, eine integrierte Lösung zur Handhabung dieses Problems implementiert (und arbeiten weiter daran) mit einer Funktion namens Suspense.
Suspense tut fast, was der Name andeutet
Sie haben es vielleicht schon am Namen erraten, aber Suspense weist eine Komponente an, mit dem Rendern zu warten, bis eine Bedingung erfüllt ist. Genau wie wir es mit `isLoading` besprochen haben, wird das Rendern der Daten verschoben, bis die API die Daten abruft und `isLoading` auf `false` gesetzt wird. Stellen Sie es sich wie eine Komponente vor, die in einem Aufzug steht und auf die richtige Etage wartet, bevor sie aussteigt.
Derzeit kann Suspense nur verwendet werden, um Komponenten bedingt zu laden, die `React.lazy()` verwenden, um dynamisch ohne Seitenneuladung zu rendern. Sagen wir also, wir haben eine Karte, deren Laden etwas Zeit in Anspruch nimmt, wenn der Benutzer einen Standort auswählt. Wir können diese Kartenkomponente mit Suspense umschließen und etwas wie den Apple Beachball des Todes aufrufen, um es anzuzeigen, während wir auf die Karte warten. Dann, sobald die Karte geladen ist, treten wir den Ball weg.
// Import the Map component
const Map = React.lazy(() => import('./Map'));
function AwesomeComponent() [
return (
// Show the <Beachball> component until the <Map> is ready
<React.Suspense fallback={<Beachball />}>
<div>
<Map />
</div>
</React.Suspense>
);
}
Genau. Ziemlich geradlinig bisher, hoffe ich.
Aber was ist, wenn wir den Fallback-Beachball nicht für eine geladene Komponente, sondern beim Warten auf Daten von einer API haben wollen. Nun, das ist eine Situation, für die Suspense perfekt geeignet zu sein scheint, aber leider noch nicht *ganz* so gehandhabt wird. Aber das wird sich ändern.
In der Zwischenzeit können wir eine experimentelle Funktion namens react-cache (das Paket, das zuvor als simple-cache-provider bekannt war) verwenden, um zu demonstrieren, wie Suspense mit API-Abrufen in Zukunft funktionieren sollte.
Lassen Sie uns Suspense trotzdem mit API-Daten verwenden
Okay, genug der *Suspense* (Entschuldigung, konnte nicht widerstehen). Kommen wir zu einem funktionierenden Beispiel, bei dem wir eine Komponente als Fallback definieren und anzeigen, während wir darauf warten, dass eine API Daten zurückgibt.
Denken Sie daran, dass react-cache experimentell ist. Wenn ich *experimentell* sage, meine ich genau das. Selbst die Paketbeschreibung fordert uns auf, es nicht in der Produktion zu verwenden.
Das werden wir bauen: eine Liste von Benutzern, die von einer API abgerufen werden.
Also, fangen wir an!
Zuerst ein neues Projekt aufsetzen
Beginnen wir mit der Erstellung einer neuen React-Anwendung mithilfe von create-react-app.
## Could be any project name
create-react-app csstricks-react-suspense
Dies wird Ihre React-Anwendung booten. Da die Suspense API noch in der Entwicklung ist, werden wir eine andere React-Version verwenden. Öffnen Sie die Datei `package.json` im Stammverzeichnis des Projekts, bearbeiten Sie die Versionsnummern von React und React-DOM und fügen Sie das Paket `simple-cache-provider` hinzu (darauf gehen wir später ein). So sieht das aus:
"dependencies": {
"react": "16.4.0-alpha.0911da3",
"react-dom": "16.4.0-alpha.0911da3",
"simple-cache-provider": "0.3.0-alpha.0911da3"
}
Installieren Sie die Pakete, indem Sie `yarn install` ausführen.
In diesem Tutorial werden wir die Funktionalität zum Abrufen von Daten von einer API entwickeln. Wir können die Funktion `createResource()` aus `simple-cache-provider` verwenden, um dies in der Datei `src/fetcher.js` zu tun.
import { createResource } from 'simple-cache-provider';
const sleep = (duration) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, duration)
})
}
const loadProfiles = createResource(async () => {
await sleep(3000)
const res = await fetch(`https://randomuser.me/api/?results=15`);
return await res.json();
});
export default loadProfiles
Was hier passiert: Die Funktion `sleep()` blockiert den Ausführungskontext für eine bestimmte Dauer, die als Argument übergeben wird. Die Funktion `sleep()` wird dann in der Funktion `loadProfiles()` aufgerufen, um eine Verzögerung von drei Sekunden (3.000 ms) zu simulieren. Durch die Verwendung von `createResource()` zum Aufrufen der API geben wir entweder den aufgelösten Wert zurück (welcher die von der API erwarteten Daten sind) oder werfen ein Promise.
Als Nächstes erstellen wir eine Higher-Order-Component namens `withCache`, die das Caching auf der von ihr umschlossenen Komponente aktiviert. Dies tun wir in einer neuen Datei namens, kreativ, `withCache.js`. Platzieren Sie diese im `src`-Verzeichnis des Projekts.
import React from 'react';
import { SimpleCache } from 'simple-cache-provider';
const withCache = (Component) => {
return props => (
<SimpleCache.Consumer>
{cache => <Component cache={cache} {...props} />}
</SimpleCache.Consumer>
);
}
export default withCache;
Diese Higher-Order-Component verwendet `SimpleCache` aus dem Paket `simple-cache-provider`, um das Caching einer umschlossenen Komponente zu ermöglichen. Wir werden dies verwenden, wenn wir unsere nächste Komponente erstellen, das verspreche ich. Erstellen Sie in der Zwischenzeit eine weitere neue Datei in `src` namens `Profile.js` – hier werden wir die Ergebnisse, die wir von der API erhalten, durchlaufen.
import React, { Fragment } from 'react';
import loadProfiles from './fetcher'
import withCache from './withCache'
// Just a little styling
const cardWidth = {
width: '20rem'
}
const Profile = withCache((props) => {
const data = loadProfiles(props.cache);
return (
<Fragment>
{
data.results.map(item => (
<div key={item.login.uuid} className="card" style={cardWidth}>
<div>
<img src={item.picture.thumbnail} />
</div>
<p>{item.email}</p>
</div>
))
}
</Fragment>
)
});
export default Profile
Was wir hier haben, ist eine Profile-Komponente, die in `withCache` verpackt ist, die zuvor erstellte Higher-Order-Component. Nun wird alles, was wir von der API zurückbekommen (was das aufgelöste Promise ist), als Wert in der Variablen `data` gespeichert, die wir als Props für die Profildaten definiert haben, die an die Komponenten mit Cache ( `props.cache`) übergeben werden.
Um den Ladezustand der Anwendung zu handhaben, bevor die Daten von der API zurückgegeben werden, implementieren wir eine Platzhalterkomponente, die gerendert wird, bevor die API mit den gewünschten Daten antwortet.
Das soll der Platzhalter tun: eine Fallback-Benutzeroberfläche rendern (was ein Lade-Spinner, ein Beachball oder etwas Ähnliches sein kann), bevor die API antwortet, und wenn die API antwortet, die Daten anzeigen. Wir möchten auch eine Verzögerung (`delayMs`) implementieren, die sich für Szenarien eignet, in denen kaum ein Lade-Spinner angezeigt werden muss. Zum Beispiel: Wenn die Daten in weniger als zwei Sekunden zurückkommen, ist ein Lader vielleicht etwas albern.
Die Platzhalterkomponente sieht dann so aus:
const Placeholder = ({ delayMs, fallback, children }) => {
return (
<Timeout ms={delayMs}>
{didTimeout => {
return didTimeout ? fallback : children;
}}
</Timeout>
);
}
`delayMs`, `fallback` und `children` werden von der `App`-Komponente, die wir gleich sehen werden, an die `Placeholder`-Komponente übergeben. Die `Timeout`-Komponente gibt einen booleschen Wert zurück, den wir verwenden können, um entweder die Fallback-Benutzeroberfläche oder die `children` der `Placeholder`-Komponente (in diesem Fall die `Profile`-Komponente) zurückzugeben.
Hier ist das endgültige Markup unserer App, das alle von uns behandelten Komponenten zusammenfügt, plus einige dekorative Markups von Bootstrap, um ein vollständiges Seitenlayout zu erstellen.
class App extends React.Component {
render() {
return (
<React.Fragment>
// Bootstrap Containers and Jumbotron
<div className="App container-fluid">
<div className="jumbotron">
<h1>CSS-Tricks React Suspense</h1>
</div>
<div className="container">
<div>
// Placeholder contains Suspense and wraps what needs the fallback UI
<Placeholder
delayMs={1000}
fallback={
<div className="row">
<div className="col-md">
<div className="div__loading">
<Loader />
</div>
</div>
</div>
}
>
<div className="row">
// This is what will render once the data loads
<Profile />
</div>
</Placeholder>
</div>
</div>
</div>
</React.Fragment>
);
}
}
Das war's!
Ziemlich raffiniert, oder? Es ist großartig, dass wir dabei sind, echte Fallback-UI-Unterstützung direkt aus der React-Box zu erhalten, ohne clevere Tricks oder zusätzliche Bibliotheken. Das ergibt absolut Sinn, da React dafür entwickelt wurde, Zustände zu verwalten, und das Laden ein häufiger zu handhabender Zustand ist.
Denken Sie daran, so großartig Suspense auch ist (und es ist wirklich großartig), es ist wichtig zu beachten, dass es sich noch in der experimentellen Phase befindet und für Produktionsanwendungen unpraktisch ist. Da es jedoch Möglichkeiten gibt, es heute zu nutzen, können wir damit in einer Entwicklungsumgebung beliebig viel herumspielen, also experimentieren Sie ruhig!
Leute, die an und mit Suspense gearbeitet haben, haben ihre Gedanken und Erfahrungen aufgeschrieben. Hier sind einige, die es wert sind, angeschaut zu werden: