Accueil / Articles

Élimination du wrapper fournisseur : contexte de rendu des composants serveur

Découvrez comment React 19.3 permet à un composant serveur de rendre directement un contexte client, comment Flight le serialise, et quels sont les limites en matière de lecture ainsi que les valeurs qui s’appliquent encore.

Éditeur : REACTAPP.TOP7 min de lecture1602 mots

Sources: React 19.3: Render a Context Directly in a Server Component, No Provider Wrapper

Presque tous les projets utilisant des React Server Components contiennent un petit fichier 'use client' dont la seule fonction est de créer un Context et d’exporter un composant fournisseur qui transmet une seule propriété. Ce wrapper existait parce qu’un composant serveur n’était pas autorisé à afficher le Context lui-même. React 19.3 supprime cette restriction, permettant ainsi au serveur d’afficher directement l’élément Context et de supprimer le wrapper.

Ce guide explique le modèle ancien, le nouveau, ce que fait réellement le protocole Flight en arrière-plan, ainsi que les pièges qui déterminent si ce changement fonctionne dans votre framework actuel. Les exemples nécessitent un environnement de exécution réel pour les Server Components (un framework prenant en charge le bundler RSC et disposant d’un serveur) ; considérez-les donc comme du code de référence complet plutôt que quelque chose à coller dans un environnement d’essai. Pour en savoir plus sur la manière dont les parties serveur et client d’une application RSC s’intègrent, consultez notre aperçu de l’architecture sous-jacente aux React Server Components.

Le modèle ancien : un composant qui ne fait que transmettre une propriété

Les composants serveur ne peuvent pas appeler createContext, car un Context représente un mécanisme d’état côté client. Pourtant, l’arborescence client qui les entoure a régulièrement besoin de données déjà disponibles sur le serveur, telles que l’utilisateur connecté, les paramètres fonctionnels d’un locataire ou la langue de la requête. La seule façon de transmettre ces données était via les props d’un composant client, qui s’occupait ensuite de rendre le provider.

Le module client commence par la directive et l’import habituels.

'use client';
import { createContext } from 'react';

Il crée ensuite le Context et exporte un provider dont le corps ne fait rien d’autre que de transmettre currentUser en tant que value.

export const UserContext = createContext(null);// exists only to forward one prop
export function UserProvider({ currentUser, children }) {
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

Côté composant serveur

Le layout, qui est un composant serveur, importe le wrapper plutôt que le Context.

import { UserProvider } from './user-context';

Il récupère l’utilisateur sur le serveur et le transmet au wrapper sous forme de prop.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserProvider currentUser={currentUser}>
      {children}
    </UserProvider>
  );
}

Si vous essayez d’ignorer l’enveloppe dans React 19.2, vous rencontrez une erreur de temps d’exécution déroutante commençant par « Element type is invalid », suivie d’une indication selon laquelle un type d’élément différé se résout en un Context plutôt qu’en une classe ou une fonction. Il s’agit apparemment d’un problème de bundler, mais la cause réside dans le rendeur. Un Context importé au-delà de la frontière client parvient au rendeur serveur sous forme d’une référence client résolue de manière différée, et 19.2 ne pouvait transformer ces références que en composants. Un objet Context n’a jamais été considéré comme un résultat acceptable.

Le nouveau schéma : rendre le Context lui-même

Dans 19.3, le module client se réduit à la directive, à l’import et à une seule export.

'use client';
import { createContext } from 'react';

Il n’y a plus de composant, seulement l’objet Context.

export const UserContext = createContext(null);

Le rendu depuis le layout

Le layout importe désormais directement UserContext.

import { UserContext } from './user-context';

Le JSX a la même structure qu’auparavant, à ceci près que l’élément est le Context et que la propriété s’appelle value.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

L’utilisation côté client

Rien ne change pour les utilisateurs. Un composant client situé en dessous du layout importe le même Context et le lit à l’aide de use.

'use client';
import { use } from 'react';
import { UserContext } from './user-context';

Ici, le menu d’avatars récupère l’utilisateur depuis le Context et affiche son image.

export function AvatarMenu() {
  const user = use(UserContext);
  return <img src={user.avatar} alt={user.name} />;
}

Faites attention à la syntaxe de React 19 dans le layout : il s’agit de <UserContext value> et non de <UserContext.Provider value>. La forme .Provider continue de fonctionner côté client, mais le serveur affiche l’objet Context lui-même, il est donc préférable d’utiliser la forme plus courte dans le code serveur.

Ce que fait Flight, et ce que cette fonctionnalité n’est pas

Lorsqu’un composant serveur rend <UserContext value={currentUser}>, aucune logique de Context n’est exécutée du tout sur le serveur. Le sérialiseur Flight émet un élément de type référence client, pointant vers le module user-context.js et son export UserContext. Cet élément contient { value: currentUser, children } comme propriétés. Ces propriétés sont sérialisées exactement comme n’importe quelles autres propriétés envoyées au client. Une fois que le navigateur charge ce module, la référence client se transforme en l’objet Context réel, et React crée un fournisseur contenant la valeur transmise. La correction apportée au rendu, documentée dans facebook/react #35675, a permis à la voie de code de type différé d’accepter un objet Context en plus des fonctions, memo et forwardRef.

Deux conséquences découlent de cette conception.

La valeur doit être sérialisable

La valeur Contexte obéit aux mêmes règles que toute propriété transmise au client. Les types autorisés incluent des primitives telles que des chaînes de caractères et des nombres, des objets simples et des tableaux, des instances Date, Map et Set, des promesses, des éléments JSX et des fonctions Server. Les fonctions ordinaires, les instances de classe ainsi que tout élément disposant d’un prototype personnalisé ne sont pas autorisés. Un enregistrement simple currentUser fonctionne ; une instance UserService contenant des méthodes ne le fait pas. Le document 'use client' présente la liste complète.

Ce n’est pas un Contexte côté serveur

La demande de fusion précise clairement qu’il est toujours impossible d’appeler use(Context) à l’intérieur d’un composant Server. Les composants Server ne disposent pas de moyen pour lire un Contexte, car aucun fournisseur n’existe sur le serveur à leur disposition. Cette fonctionnalité ne sert qu’à simplifier la transmission d’une valeur vers l’arborescence client.

Pourquoi il vaut la peine de supprimer l’enveloppe

Auparavant, chaque valeur partagée nécessitait un fichier et une composante. Cela semblait anodin tant que l’on ne les comptait pas : une application disposant de contextes pour l’utilisateur, le thème, la localisation, les flags fonctionnels, le locataire et les analyses impliquait six enveloppes presque identiques, chacune représentant un risque d’oublier un paramètre ou de taper par erreur une valeur par défaut. Avec la nouvelle approche, le fournisseur se trouve directement là où les données sont récupérées, ce qui fait disparaître un cas particulier dans le modèle mental consistant à déterminer « quels éléments s’exécutent sur le serveur et lesquels sur le client ».

La réaction de la communauté face à ce changement a été principalement un soulagement plutôt qu’une excitation. Les développeurs ont commenté qu’il était étrange que cela n’ait pas été pris en charge dès le début, étant donné qu’une valeur de contexte n’est en réalité que des données stockées en mémoire. Cette intuition est juste, et elle explique pourquoi la correction apportée dans React lui-même a été mineure.

Risques à considérer avant de migrer

  • Votre framework doit inclure les paquets Flight 19.3. La modification concerne react-server-dom-webpack, -turbopack et -parcel ainsi que le mécanisme de conciliation, et non seulement react. Vérifiez la version de React que votre framework impose ; une version inadaptée provoque le même erreur « Element type is invalid » décrite précédemment.
  • Définissez le Context dans un fichier marqué 'use client'. Un Context défini dans du code serveur ne peut pas être rendu de cette manière, car tout le mécanisme dépend de l’existence de cet objet dans le bundle client.
  • La lecture sur le serveur reste impossible. Un composant serveur qui a besoin de cette valeur doit la recevoir en tant qu’argument ou la récupérer lui-même ; use(Context) génère une erreur dans ce cas.
  • Seules les valeurs serialisables fonctionnent. Si la valeur précédente était une instance d’une classe ou possédait des méthodes associées, l’encapsuleur les masquait, et le problème apparaît alors sous forme d’erreur de sérialisation.
  • Une promesse est une valeur valide. Étant donné que les promesses peuvent être serialisées à travers les frontières, l’écriture de <UserContext value={getCurrentUser()}> sans await transmet la valeur en flux, et les consommateurs doivent l’déencapsuler deux fois à l’aide de use(use(UserContext)). Cela peut constituer une technique de flux utile, mais il est également facile de commettre une erreur en omettant un await.
  • Les valeurs par défaut restent une préoccupation du client. La valeur par défaut transmise à createContext(null) s’applique partout où aucun fournisseur n’est situé au-dessus d’un consommateur, et le rendu du fournisseur sur le serveur ne modifie pas cela.
  • Pour l’instant, rester avec l’encapsuleur

    Si votre framework n’a pas encore adopté la version 19.3, conservez le wrapper. Le modèle UserProvider fonctionne avec toutes les versions de React 19 et dans tous les frameworks. Lorsque vous ferez la mise à niveau, la migration sera simple :

    • Supprimez la fonction wrapper du module client.
    • Importez le Context dans le composant serveur à la place du wrapper.
    • Rénommez la propriété transmise en value.

    Points clés

    • Avec React 19.3, un composant serveur peut afficher un Context exporté depuis un module 'use client', ce qui rend les wrappers de fournisseurs à usage unique inutiles.
  • Au niveau technique, Flight envoie une référence du client ainsi que des propriétés sérialisées ; aucun comportement lié au Context n’est exécuté sur le serveur.
  • La valeur doit respecter les règles normales de sérialisation, et les composants serveur ne peuvent toujours pas lire le Context.
  • Vérifiez que votre framework inclut bien les paquets correspondants de Flight avant de supprimer les enveloppes, et faites attention aux valeurs de promesse accidentelles.
  • Pour plus de détails, consultez les notes de version de React 19.3 sur react.dev ainsi que la documentation des composants serveur.