React 18 sera la prochaine version majeure de la populaire bibliothèque de composants JavaScript. Maintenant disponible en tant que version candidate, elle introduit plusieurs changements pour améliorer les récupérations de données, les performances et le rendu côté serveur.

Pour profiter de toutes les fonctionnalités, vous devrez mettre à jour votre projet et vous risquez de rencontrer quelques changements de rupture. React 18 reste cependant généralement rétrocompatible avec les codes plus anciens. Vous devriez être en mesure de passer à la version release dans votre package.json sans rencontrer trop de problèmes immédiats.

Rendu simultané

La motivation derrière la plupart des révisions de React 18 concerne quelque chose appelé « rendu simultané ». Ce mécanisme donne à React un moyen d’assembler plusieurs versions de votre arbre de composants simultanément. Bien que les détails de ce mécanisme ne concernent que les aspects internes de la bibliothèque, le résultat est une flexibilité accrue et des performances améliorées pour votre application.

Le rendu simultané rend le processus de rendu interruptible. Alors qu’un rendu dans React 17 doit être exécuté jusqu’au bout une fois qu’il a commencé, React 18 offre un moyen d’interrompre le processus à mi-chemin et de le reprendre plus tard.

Cette possibilité signifie que les rendus React sont moins susceptibles d’avoir un impact sur les performances globales du navigateur. Jusqu’à présent, les événements du navigateur tels que les pressions sur les touches et les peintures sont bloqués pendant qu’un rendu est en cours. Avec le rendu simultané activé, une pression sur une touche interrompra le rendu, permettra au navigateur de gérer le changement, puis reprendra le rendu.

L’utilisateur bénéficie ainsi d’une expérience plus fluide, moins susceptible de bégayer lorsque le rendu coïncide avec d’autres activités. React maintient plusieurs branches de travail ; une fois que le rendu d’une branche est terminé, ce qui peut se produire au cours de plusieurs sessions distinctes, il est accepté dans la branche principale qui produit l’interface utilisateur visible.

Mode simultané et rendu simultané

Avant que React 18 n’atteigne le statut alpha, cette fonctionnalité était appelée « mode simultané ». Vous pouvez encore voir ce nom dans d’anciens articles et documentations. Le concept de rendu simultané en tant que mode distinct n’existe plus dans React 18. Cela facilite la migration des applications existantes vers la nouvelle approche.

Le rendu simultané est fondamentalement différent du système de rendu existant. Il dispose d’une API entièrement nouvelle qui remplace la fonction familière ReactDOM.render(). À l’époque du mode simultané, la concurrence était tout ou rien : soit elle était activée pour votre application, avec la perspective de changements majeurs, soit elle était complètement hors limites. Maintenant, elle est gérée plus gracieusement, React n’appliquant le rendu concurrent qu’aux mises à jour du DOM qui requièrent réellement une fonctionnalité concurrente.

La nouvelle API racine (activation du mode simultané)

Les applications existantes mises à jour vers React 18 peuvent continuer à utiliser ReactDOM.render() dans un avenir proche. Cela rendra votre application sans le support de la simultanéité, en utilisant le moteur de rendu familier de la v17. Vous verrez un avertissement dans la console indiquant que l’API n’est plus supportée, mais vous pouvez en faire abstraction pendant la mise à jour.

import App de « ./App.js » ;
import ReactDOM de « react-dom » ;

// « ReactDOM.render n’est plus supporté par React 18 ».
ReactDOM.render(, document.getElementById(« root »)) ;

Pour supprimer l’avertissement, passez à la nouvelle API createRoot() :

import {createRoot} de « react-dom/client » ;
const root = createRoot(document.getElementById(« root »)) ;
root.render() ;

createRoot() renvoie un nouvel objet racine qui représente une surface de rendu React. Vous pouvez appeler sa méthode render() pour rendre un composant React à la racine. Le résultat du code ci-dessus est le même que celui de l’exemple précédent ReactDOM.render(). createRoot() est une interface plus orientée objet et plus facile à utiliser.

Les racines produites par createRoot() prennent en charge le rendu simultané. La mise à niveau vers cette API vous donne un accès optionnel aux nouvelles capacités de React 18.

L’équivalent createRoot() de ReactDOM.unmountComponentAtNode() est la nouvelle méthode unmount() exposée sur les objets racine. Vous pouvez l’utiliser pour détacher votre arbre de composants React et arrêter le rendu de votre application :

import App de « ./App.js » ;
import ReactDOM de « react-dom » ;

// ANCIEN
ReactDOM.unmountComponentAtNode(document.getElementById(« root »)) ;

// NOUVEAU
const root = createRoot(document.getElementById(« root »)) ;
root.unmount() ;

Fonctionnalités simultanées

Le rendu simultané vous permet d’utiliser des fonctionnalités concurrentes pour améliorer les performances de votre application. Voici quelques-unes des principales API disponibles.

Suspense

Le composant existe depuis React 16. Il vous permet d’empêcher le rendu des enfants d’un composant jusqu’à ce qu’une condition soit remplie. Il est couramment utilisé pour les récupérations de données et les importations asynchrones de modules.

const fetchPostHistory = id => fetch(/users/${id}/posts);

const UserCard = ({Id, Name}) => {

const [postHistory] = useState(() => fetchPostHistory(Id));

<div>
    <h1>{Name}</h1>
    <React.Suspense fallback="Loading...">
        <UserPostHistoryList posts={postHistory} />
        <ReportUserLink id={Id} />
    </React.Suspense>
</div>

};

Dans cet exemple, ni le composant UserPostHistory ni le composant ReportUserLink n’apparaîtront tant que l’historique des messages de l’utilisateur n’aura pas été récupéré sur le réseau. Cela fonctionne déjà bien dans de nombreuses situations, mais l’implémentation de React 17 présente quelques bizarreries.

Si vous enregistrez les effets de chaque composant, vous verrez que le composant ReportUserLink est rendu alors que les messages sont encore en cours de chargement, même s’il n’est pas visible à ce moment-là.

En utilisant l’explication de la concurrence de tout à l’heure, il est possible d’expliquer pourquoi : une fois que React a commencé à rendre l’arbre des composants, il n’avait aucun moyen de s’arrêter, même si un humain peut repérer que ReportUserLink est redondant jusqu’à ce que postHistory soit rempli.

Le suspens est plus puissant dans React 18. The new version is called “Concurrent Suspense”; the previous implementation is now referred to as Legacy Suspense. It solves the problem in the example above: rendering the same code with concurrency enabled will prevent the renderer reaching while the data fetch is ongoing.

Logging each component’s effects would show that ReportUserLink only gets committed once the post history is available. React interrompt le rendu lorsqu’il atteint UserPostHistoryList et doit attendre le chargement des données. Une fois l’appel réseau terminé, React reprend le rendu du reste de la sous-arborescence Suspense.

Cette capacité permet d’éviter le gaspillage de travail dont vos utilisateurs ne profitent jamais. Elle résout également plusieurs problèmes avec Suspense où les composants peuvent avoir exécuté des effets plus tôt que prévu. Enfin, cette solution offre une garantie automatique que les données arriveront dans l’ordre où elles ont été demandées. Vous n’avez pas à vous soucier des conditions de course puisque le rendu est interrompu pendant que les données sont récupérées.

Transitions

Les transitions sont une nouvelle fonctionnalité activée en mode simultané. Cette API est un moyen de signaler à React les priorités relatives des mises à jour de votre interface utilisateur. Une « transition » est une mise à jour relativement peu prioritaire, comme le passage d’un écran principal à un autre. Les mises à jour telles que les réexpositions en réponse à la saisie au clavier et à d’autres interactions de l’utilisateur sont considérées comme plus urgentes.

Marquer une mise à jour comme une transition a quelques effets sur la façon dont React aborde son exécution. React utilisera les capacités de rendu interruptible de la concurrence pour mettre en pause la mise à jour si une mise à jour plus urgente survient à mi-chemin. Cela permettra à votre interface utilisateur de rester réactive aux entrées de l’utilisateur pendant que le rendu est en cours, réduisant ainsi le bégaiement et le janking.

Les transitions sont utiles dans un large éventail de situations : la mise à jour d’un panneau de notifications dans l’en-tête de votre application, la gestion des mises à jour de votre barre latérale et la modification d’autres fonctions auxiliaires de votre interface utilisateur sont toutes de bonnes candidates. Ils fonctionnent également bien pour les actions asynchrones effectuées en réponse à une entrée de l’utilisateur, comme le cas classique d’une barre de recherche qui se met à jour lorsque l’utilisateur tape.

Ce type d’action peut être difficile à mettre en œuvre dans React – sans un débouclage minutieux, il est fréquent de ressentir un décalage perceptible, car les mises à jour causées par la récupération de nouveaux résultats bloquent temporairement le thread principal, qui n’a pas le temps de traiter les saisies au clavier. Avec React 18, vous pouvez utiliser une transition pour marquer ces mises à jour comme un travail de faible priorité.

L’API startTransition() encapsule les mises à jour d’état sous forme de transitions :

import {startTransition} de « react » ;

const Component = () => {

const [searchQuery, setSearchQuery] = useState("") ;
const [searchResults, setSearchResults] = useState({});

/**
 * State updates within the transition function are low-priority
 */
startTransition(() => {
    setSearchResults({text: "Search Result 1"});
});

};

Si vous souhaitez vérifier si une mise à jour est en cours, remplacez la simple startTransition() par le hook useTransition(). Cela vous donne un booléen indiquant si une transition a un travail en cours.

import {useTransition} de « react » ;

const Component = () => {

const [searchQuery, setSearchQuery] = useState("") ;
const [searchResults, setSearchResults] = useState({});

const [isSearching, startSearchResultsTransition] = useTransition();

startSearchResultsTransition(() => {
    setSearchResults({text: "Search Result 1"});
});

return (
    <div>
        <input onChange={setSearchQuery} value={searchQuery} />
        <SearchResults results={searchResults} />
        {(isSearching && "(Searching...)")}
    </div>
) ;

} ;

Toutes les mises à jour d’état existantes sont traitées comme des mises à jour urgentes régulières afin de maintenir la rétrocompatibilité avec l’ancien code.

Valeurs différées

Les valeurs différées sont un autre moyen de maintenir la réactivité pendant les mises à jour de longue durée. Lorsqu’une valeur est différée par le hook useDeferredValue(), React continuera à afficher son ancienne valeur pendant une période déterminée.

const Component = () => {
const [results, setResults] = useState([]) ;
const deferredResults = useDeferredResults(results, {timeoutMs : 5000}) ;
retourne ;
} ;

Permettre à React de continuer à afficher les anciens résultats pendant cinq secondes évite les ralentissements en supprimant la nécessité de rendre immédiatement les données récupérées dès qu’elles arrivent. Il s’agit d’une forme de débouclage intégrée à la gestion de l’état de React. Les données peuvent accuser un retard de quelques secondes par rapport à l’état réel afin de réduire la quantité globale de travail effectuée.

Meilleure mise en lot

Un dernier changement orienté vers les performances dans React 18 consiste en un ensemble d’améliorations du batching des mises à jour d’état. React essaie déjà de combiner les mises à jour d’état dans plusieurs situations simples :

const Component = () => {

const [query, setQuery] = useState("") ;
const [queryCount, setQueryCount] = useState("") ;

/**
 * Deux mises à jour d'état, un seul re-rendu
 */
setQuery("demo") ;
setQueryCount(queryCount + 1) ;

} ;

Cependant, il existe plusieurs situations où cela ne fonctionne pas. À partir de React 18, le batching s’applique à toutes les mises à jour, quelle que soit leur origine. Les mises à jour qui proviennent de délais, de promesses et de gestionnaires d’événements du navigateur seront entièrement mises en lots de la même manière que le code qui se trouve directement dans votre composant.

Ce changement peut modifier le comportement de certains codes. Si vous avez un ancien composant qui met à jour l’état plusieurs fois dans les endroits énumérés ci-dessus, puis vérifie les valeurs à mi-chemin, vous pourriez trouver qu’ils ne sont pas ce que vous attendez dans React 18. Une méthode flushSync est disponible pour forcer manuellement une mise à jour d’état à commiter, vous permettant de ne pas utiliser le batching.

const Component = () => {

const [query, setQuery] = useState("") ;
const [queryCount, setQueryCount] = useState("") ;

const handleSearch = query => {
   fetch(query).then(() => {

        /**
         * Force le commit et met à jour le DOM
         */
        flushSync(() => setQuery(query)) ;

        setQueryCount(1) ;

    }) ;
}

} ;

Modifications du rendu côté serveur

Le rendu côté serveur a été fortement modifié. La principale nouveauté est la prise en charge du rendu en continu, où le nouveau HTML peut être transmis en continu du serveur à votre client React. Cela vous permet d’utiliser les composants de Suspense côté serveur.

En conséquence de ce changement, plusieurs API ont été dépréciées ou retravaillées, notamment renderToNodeStream(). Vous devez désormais utiliser renderToPipeableStream() ou renderToReadableStream() pour fournir un contenu côté serveur compatible avec les environnements de streaming modernes.

L’hydratation côté client du contenu rendu par le serveur a également changé pour s’aligner sur la nouvelle API de rendu simultané. Si vous utilisez le rendu serveur et le mode concurrent, remplacez hydrate() par hydrateRoot() :

// ANCIEN
import {hydrate} de « react-dom » ;
hydrate(, document.getElementById(« root »)) ;

// NOUVEAU
import {hydrateRoot} de « react-dom/client » ;
hydrateRoot(document.getElementById(« root »), ) ;

Les effets du rendu en continu rendent le rendu serveur plus adapté à des cas d’utilisation variés. L’implémentation existante de React côté serveur nécessite que le client récupère et hydrate l’ensemble de l’application avant qu’elle ne devienne interactive. En ajoutant les flux et Suspense, React peut récupérer uniquement les éléments nécessaires au rendu initial, puis charger les données supplémentaires non essentielles depuis le serveur une fois que l’application est interactive.

Conclusion

React 18 apporte de nouvelles fonctionnalités et des améliorations de performance pour vos applications. Des capacités comme Suspense et Transitions rendent plusieurs types de code plus faciles à écrire et moins impactants sur d’autres zones de votre app.

La mise à niveau vers React 18 lorsqu’il sortira devrait être assez facile dans la plupart des cas. Vous pouvez continuer à utiliser l’API racine de React 17 pour le moment avant de passer à createRoot() lorsque vous serez prêt à adopter le rendu simultané. Si vous voulez commencer à préparer votre application aujourd’hui, vous pouvez installer la dernière version candidate en exécutant npm install [email protected] [email protected]