Maîtrisez React Suspense avec des modèles pratiques pour une récupération de données efficace, des états de chargement et une gestion robuste des erreurs. Créez des expériences utilisateur plus fluides et résilientes.
Modèles React Suspense : Récupération de données et Error Boundaries
React Suspense est une fonctionnalité puissante qui vous permet de "suspendre" le rendu des composants en attendant la fin des opérations asynchrones, telles que la récupération de données. Associé aux Error Boundaries, il offre un mécanisme robuste pour gérer les états de chargement et les erreurs, résultant en une expérience utilisateur plus fluide et plus résiliente. Cet article explore divers modèles pour exploiter efficacement Suspense et les Error Boundaries dans vos applications React.
Comprendre React Suspense
À la base, Suspense est un mécanisme qui permet à React d'attendre quelque chose avant de rendre un composant. Ce "quelque chose" est généralement une opération asynchrone, comme la récupération de données depuis une API. Au lieu d'afficher un écran vide ou un état intermédiaire potentiellement trompeur, vous pouvez afficher une interface utilisateur de secours (par exemple, un indicateur de chargement) pendant que les données sont chargées.
Le principal avantage est l'amélioration de la performance perçue et une expérience utilisateur plus agréable. Les utilisateurs reçoivent immédiatement un retour visuel indiquant que quelque chose se passe, plutôt que de se demander si l'application est figée.
Concepts Clés
- Composant Suspense : Le composant
<Suspense>enveloppe les composants susceptibles de se suspendre. Il accepte une propfallback, qui spécifie l'interface utilisateur à rendre pendant que les composants enveloppés sont suspendus. - Interface utilisateur de secours : C'est l'interface utilisateur affichée pendant que l'opération asynchrone est en cours. Cela peut être n'importe quoi, d'un simple indicateur de chargement à une animation plus élaborée.
- Intégration de Promesse : Suspense fonctionne avec les Promesses. Lorsqu'un composant tente de lire une valeur à partir d'une Promesse qui n'a pas encore été résolue, React suspend le composant et affiche l'interface utilisateur de secours.
- Sources de données : Suspense repose sur des sources de données compatibles avec Suspense. Ces sources exposent une API qui permet à React de détecter quand les données sont en cours de récupération.
Récupération de données avec Suspense
Pour utiliser Suspense pour la récupération de données, vous aurez besoin d'une bibliothèque de récupération de données compatible avec Suspense. Voici une approche courante utilisant une fonction `fetchData` personnalisée :
Exemple : Récupération de données simple
Tout d'abord, créez une fonction utilitaire pour la récupération de données. Cette fonction doit gérer l'aspect 'suspension'. Nous allons envelopper nos appels fetch dans une ressource personnalisée pour gérer correctement l'état de la promesse.
// 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;
Maintenant, créons un composant qui utilise Suspense pour afficher les données utilisateur :
// 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;
Enfin, enveloppez le composant UserProfile avec <Suspense> :
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
function App() {
return (
<Suspense fallback={<p>Chargement des données utilisateur...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
Dans cet exemple, le composant UserProfile tente de lire les données user de la resource. Si les données ne sont pas encore disponibles (la Promesse est toujours en attente), le composant se suspend, et l'interface utilisateur de secours ("Chargement des données utilisateur...") est affichée. Une fois les données récupérées, le composant se re-rend avec les informations utilisateur réelles.
Avantages de cette approche
- Récupération de données déclarative : Le composant exprime *quelles* données il a besoin, pas *comment* les récupérer.
- Gestion centralisée de l'état de chargement : Le composant Suspense gère l'état de chargement, simplifiant la logique du composant.
Error Boundaries pour la résilience
Alors que Suspense gère les états de chargement avec élégance, il ne gère pas intrinsèquement les erreurs qui pourraient survenir lors de la récupération de données ou du rendu des composants. C'est là qu'interviennent les Error Boundaries.
Les Error Boundaries sont des composants React qui interceptent les erreurs JavaScript n'importe où dans leur arborescence de composants enfants, enregistrent ces erreurs et affichent une interface utilisateur de secours au lieu de faire planter toute l'application. Ils sont essentiels pour construire des interfaces utilisateur résilientes qui peuvent gérer les erreurs inattendues avec élégance.
Créer une Error Boundary
Pour créer une Error Boundary, vous devez définir un composant de classe qui implémente les méthodes de cycle de vie static getDerivedStateFromError() et 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) {
// Mettre à jour l'état afin que le prochain rendu affiche l'interface utilisateur de secours.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, errorInfo) {
// Vous pouvez également enregistrer l'erreur auprès d'un service de rapport d'erreurs
console.error("Erreur interceptée : ", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// Vous pouvez rendre n'importe quelle interface utilisateur de secours personnalisée
return (
<div>
<h2>Quelque chose s'est mal passé.</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;
La méthode getDerivedStateFromError est appelée lorsqu'une erreur est lancée dans un composant descendant. Elle met à jour l'état pour indiquer qu'une erreur s'est produite.
La méthode componentDidCatch est appelée après qu'une erreur a été lancée. Elle reçoit l'erreur et les informations d'erreur, que vous pouvez utiliser pour enregistrer l'erreur auprès d'un service de rapport d'erreurs ou afficher un message d'erreur plus informatif.
Utilisation des Error Boundaries avec Suspense
Pour combiner les Error Boundaries avec Suspense, enveloppez simplement le composant <Suspense> avec un composant <ErrorBoundary> :
// App.js
import React, { Suspense } from 'react';
import UserProfile from './components/UserProfile';
import ErrorBoundary from './components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<p>Chargement des données utilisateur...</p>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}
export default App;
Maintenant, si une erreur se produit lors de la récupération des données ou du rendu du composant UserProfile, l'Error Boundary interceptera l'erreur et affichera l'interface utilisateur de secours, empêchant l'application entière de planter.
Modèles Suspense Avancés
Au-delà de la récupération de données de base et de la gestion des erreurs, Suspense offre plusieurs modèles avancés pour construire des interfaces utilisateur plus sophistiquées.
Découpage de code avec Suspense
Le découpage de code est le processus de division de votre application en plus petits morceaux qui peuvent être chargés à la demande. Cela peut améliorer considérablement le temps de chargement initial de votre application.
React.lazy et Suspense facilitent incroyablement le découpage de code. Vous pouvez utiliser React.lazy pour importer dynamiquement des composants, puis les envelopper avec <Suspense> pour afficher une interface utilisateur de secours pendant que les composants sont en cours de chargement.
// components/MyComponent.js
import React from 'react';
const MyComponent = React.lazy(() => import('./AnotherComponent'));
function App() {
return (
<Suspense fallback={<p>Chargement du composant...</p>}>
<MyComponent />
</Suspense>
);
}
export default App;
Dans cet exemple, le MyComponent est chargé à la demande. Pendant son chargement, l'interface utilisateur de secours ("Chargement du composant...") est affichée. Une fois le composant chargé, il est rendu normalement.
Récupération de données parallèle
Suspense vous permet de récupérer plusieurs sources de données en parallèle et d'afficher une seule interface utilisateur de secours pendant que toutes les données sont chargées. Cela peut être utile lorsque vous devez récupérer des données de plusieurs API pour rendre un seul composant.
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>Chargement des données utilisateur et des posts...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
Dans cet exemple, le composant UserProfile récupère les données utilisateur et les données des posts en parallèle. Le composant <Suspense> affiche une seule interface utilisateur de secours pendant que les deux sources de données sont en cours de chargement.
API de transition avec useTransition
React 18 a introduit le hook useTransition, qui améliore Suspense en offrant un moyen de gérer les mises à jour de l'interface utilisateur comme des transitions. Cela signifie que vous pouvez marquer certaines mises à jour d'état comme moins urgentes et les empêcher de bloquer l'interface utilisateur. C'est particulièrement utile pour gérer des récupérations de données plus lentes ou des opérations de rendu complexes, améliorant ainsi la performance perçue.
Voici comment vous pouvez utiliser 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}>
Afficher le profil utilisateur
</button>
{isPending && <p>Chargement...</p>}
<Suspense fallback={<p>Chargement des données utilisateur...</p>}>
{showProfile && <UserProfile />}
</Suspense>
</div>
);
}
export default App;
Dans cet exemple, cliquer sur le bouton "Afficher le profil utilisateur" initie une transition. startTransition marque la mise à jour de setShowProfile comme une transition, permettant à React de prioriser d'autres mises à jour de l'interface utilisateur. La valeur isPending de useTransition indique si une transition est en cours, vous permettant de fournir un retour visuel (par exemple, en désactivant le bouton et en affichant un message de chargement).
Bonnes pratiques pour l'utilisation de Suspense et des Error Boundaries
- Enveloppez Suspense autour de la plus petite zone possible : Évitez d'envelopper de grandes parties de votre application avec
<Suspense>. Au lieu de cela, n'enveloppez que les composants qui ont réellement besoin de se suspendre. Cela minimisera l'impact sur le reste de l'interface utilisateur. - Utilisez des interfaces utilisateur de secours significatives : L'interface utilisateur de secours doit fournir aux utilisateurs un retour clair et informatif sur ce qui se passe. Évitez les indicateurs de chargement génériques ; essayez plutôt de fournir plus de contexte (par exemple, "Chargement des données utilisateur...").
- Placez les Error Boundaries de manière stratégique : Réfléchissez attentivement à l'endroit où placer les Error Boundaries. Placez-les suffisamment haut dans l'arborescence des composants pour intercepter les erreurs qui pourraient affecter plusieurs composants, mais suffisamment bas pour éviter d'intercepter les erreurs spécifiques à un seul composant.
- Enregistrez les erreurs : Utilisez la méthode
componentDidCatchpour enregistrer les erreurs auprès d'un service de rapport d'erreurs. Cela vous aidera à identifier et à corriger les erreurs dans votre application. - Fournissez des messages d'erreur conviviaux : L'interface utilisateur de secours affichée par les Error Boundaries doit fournir aux utilisateurs des informations utiles sur l'erreur et ce qu'ils peuvent faire à ce sujet. Évitez le jargon technique ; utilisez plutôt un langage clair et concis.
- Testez vos Error Boundaries : Assurez-vous que vos Error Boundaries fonctionnent correctement en provoquant délibérément des erreurs dans votre application.
Considérations Internationales
Lorsque vous utilisez Suspense et les Error Boundaries dans des applications internationales, tenez compte des points suivants :
- Localisation : Assurez-vous que les interfaces utilisateur de secours et les messages d'erreur sont correctement localisés pour chaque langue prise en charge par votre application. Utilisez des bibliothèques d'internationalisation (i18n) comme
react-intloui18nextpour gérer les traductions. - Mises en page de droite à gauche (RTL) : Si votre application prend en charge les langues RTL (par exemple, l'arabe, l'hébreu), assurez-vous que les interfaces utilisateur de secours et les messages d'erreur sont correctement affichés dans les mises en page RTL. Utilisez les propriétés logiques CSS (par exemple,
margin-inline-startau lieu demargin-left) pour prendre en charge les mises en page LTR et RTL. - Accessibilité : Assurez-vous que les interfaces utilisateur de secours et les messages d'erreur sont accessibles aux utilisateurs handicapés. Utilisez les attributs ARIA pour fournir des informations sémantiques sur l'état de chargement et les messages d'erreur.
- Sensibilité culturelle : Soyez attentif aux différences culturelles lors de la conception des interfaces utilisateur de secours et des messages d'erreur. Évitez d'utiliser des images ou un langage qui pourraient être offensants ou inappropriés dans certaines cultures. Par exemple, un indicateur de chargement commun pourrait être perçu négativement dans certaines cultures.
Exemple : Message d'erreur localisé
En utilisant react-intl, vous pouvez créer des messages d'erreur localisés :
// components/ErrorBoundary.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
class ErrorBoundary extends React.Component {
// ... (identique Ă avant)
render() {
if (this.state.hasError) {
return (
<div>
<h2><FormattedMessage id="error.title" defaultMessage="Quelque chose s'est mal passé." /></h2>
<p><FormattedMessage id="error.message" defaultMessage="Veuillez réessayer plus tard." /></p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Ensuite, définissez les traductions dans vos fichiers de locale :
// 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."
}
Conclusion
React Suspense et les Error Boundaries sont des outils essentiels pour construire des interfaces utilisateur modernes, résilientes et conviviales. En comprenant et en appliquant les modèles décrits dans cet article, vous pouvez améliorer considérablement la performance perçue et la qualité globale de vos applications React. N'oubliez pas de prendre en compte l'internationalisation et l'accessibilité pour vous assurer que vos applications sont utilisables par un public mondial.
La récupération de données asynchrone et la gestion appropriée des erreurs sont des aspects critiques de toute application web. Suspense, combiné aux Error Boundaries, offre un moyen déclaratif et efficace de gérer ces complexités dans React, résultant en une expérience utilisateur plus fluide et plus fiable pour les utilisateurs du monde entier.