Fermer

août 31, 2021

Gestion de l'état dans Next.js —


Résumé rapide ↬

En combinant certaines API React, nous pouvons gérer avec précision des états «simples». Cependant, avec Next.js, nous pouvons rapidement trouver des situations où nous devons répondre à de nombreuses autres exigences, telles que l'isolement de l'état entre les pages ou les routes, et la gestion de nos données à la fois sur le serveur et le client tout en faisant venir des services tiers. C'est beaucoup! Jetons un coup d'œil à quelques modèles pour accomplir tout cela.

Cet article est destiné à être utilisé comme introduction à la gestion des états complexes dans une application Next.js. Malheureusement, le framework est bien trop polyvalent pour que nous puissions couvrir tous les cas d'utilisation possibles dans cet article. Mais ces stratégies devraient s'adapter à la grande majorité des applications avec peu ou pas d'ajustements. Si vous pensez qu'il existe un modèle pertinent à prendre en compte, j'ai hâte de vous voir dans la section commentaires ! composants aux composants enfants. Quelle que soit la façon dont une application gère ses données, elle doit transmettre les données de haut en bas.

À mesure qu'une application augmente en complexité et en ramifications de votre arbre de rendu, plusieurs couches apparaissent. Parfois, il est nécessaire de transmettre des données à plusieurs couches de composants parents jusqu'à ce qu'elles atteignent enfin le composant auquel les données sont destinées, cela s'appelle Prop Drilling.

Comme on pouvait s'y attendre : Prop Drilling peut devenir un modèle encombrant et sujet aux erreurs à mesure que les applications se développent. Pour contourner ce problème vient de l'API Context. L'API Context ajoute 3 éléments à cette équation :

  1. Context
    Les données qui sont transférées du fournisseur au consommateur.
  2. Context Provider
    Le composant d'où proviennent les données.
  3. Context Consumer[19659008]Le composant qui utilisera les données reçues.

Le fournisseur est invariablement un ancêtre du composant consommateur, mais il n'est probablement pas un ancêtre direct. L'API ignore ensuite tous les autres maillons de la chaîne et transmet les données (contexte) directement au consommateur. Il s'agit de l'intégralité de l'API Context, transmettant des données. Cela a autant à voir avec les données que le bureau de poste a à voir avec votre courrier.

Dans une application vanille React, les données peuvent être gérées par 2 autres API : useState et useReducer . Il serait au-delà de la portée de cet article de suggérer quand utiliser l'un ou l'autre, alors restons simples en disant :

  • useState
    Structure de données simple et conditions simples.
  • useReducer
    Structures de données complexes. et/ou des conditions entrelacées.

Le fait que le Prop Drilling et la gestion des données dans React soient confondus à tort, car un modèle est partiellement lié à une faille inhérente à l'API de contenu hérité. Lorsqu'un nouveau rendu de composant était bloqué par shouldComponentUpdatecela empêchait le contexte de continuer jusqu'à sa cible. Ce problème a poussé les développeurs à recourir à des bibliothèques tierces alors qu'ils n'avaient besoin que d'éviter le forage d'accessoires. .

Next.js est un framework React. Ainsi, toutes les solutions décrites pour les applications React peuvent être appliquées à une application Next.js. Certains auront besoin d'un plus grand flex pour le configurer, certains auront les compromis redistribués en fonction des propres fonctionnalités de Next.js. Mais tout est utilisable à 100%, vous pouvez choisir votre poison librement.

Pour la majorité des cas d'utilisation courants, la combinaison de Context et State/Reducer est suffisante. Nous allons considérer cela pour cet article et ne pas trop plonger dans les subtilités des états complexes. Nous prendrons cependant en considération le fait que la plupart des applications Jamstack reposent sur des données externes, et c'est également un état. application :

  • _document. {t,j}sx
    Ce composant est utilisé pour définir le balisage statique. Ce fichier est rendu sur le serveur et n'est pas rendu à nouveau sur le client. Utilisez-le pour affecter les balises et et d'autres métadonnées. Si vous ne souhaitez pas personnaliser ces éléments, vous pouvez les inclure dans votre application.
  • _app. {t,j}sx
    Celui-ci est utilisé pour définir la logique qui doit se diffuser dans l'application. Tout ce qui devrait être présent sur chaque vue de l'application appartient ici. Utilisez-le pour les s, les définitions globales, les paramètres d'application, etc.

Pour être plus explicite, les fournisseurs de contexte sont appliqués ici, par exemple :

// _app.jsx ou _app.tsx

importer { AppStateProvider } de './my-context'

exporter la fonction par défaut MyApp({ Component, pageProps }) {
  revenir (
    
      
    
  )
}

Chaque fois qu'un nouvel itinéraire est visité, nos pages peuvent exploiter le AppStateContext et voir leurs définitions transmises en tant que props. Lorsque notre application est assez simple, elle n'a besoin que d'une définition pour être répartie comme ceci, le modèle précédent devrait suffire. Par exemple :

exporter la fonction par défaut ConsumerPage() {
  const { état } = useAppStatecontext()
  revenir (
    

{état} est ici ! ??

) }

Vous pouvez vérifier une implémentation réelle de ce modèle ContextAPI dans notre dépôt de démonstration.

Si vous avez plusieurs éléments d'état définis dans un seul contexte, vous pouvez commencer à rencontrer des problèmes de performances. La raison en est que lorsque React voit une mise à jour d'état, il effectue tous les rendus nécessaires dans le DOM. Si cet état est partagé entre de nombreux composants (comme c'est le cas lors de l'utilisation de l'API de contexte), cela pourrait provoquer des rendus inutilesce que nous ne voulons pas. Faites preuve de discernement avec les variables d'état que vous partagez entre les composants !

Pour rester organisé avec votre partage d'état, vous pouvez créer plusieurs éléments de contexte (et donc différents fournisseurs de contexte) pour contenir différents éléments d'état. Par exemple, vous pouvez partager l'authentification dans un contexte, les préférences d'internationalisation dans un autre et le thème du site Web dans un autre.

Next.js fournit également un modèle que vous pouvez utiliser pour quelque chose comme ça, pour faire abstraction de toute cette logique du fichier _appen le gardant propre et lisible.

// _app.jsx ou _app.tsx
importer { DefaultLayout } à partir de './layout'

exporter la fonction par défaut MyApp({ Component, pageProps }) {
  const getLayout = Component.getLayout || (
    page => {page}
  )

  retour getLayout()
}



// layout.jsx
importer { AppState_1_Provider } depuis '../context/context-1'
importer { AppState_2_Provider } depuis '../context/context-2'

export const DefaultLayout = ({ enfants }) => {
  revenir (
    
      
        
{enfants}
) }

Avec ce modèle, vous pouvez créer plusieurs fournisseurs de contexte et les garder bien définis dans un composant de mise en page pour l'ensemble de l'application. De plus, la fonction getLayout vous permettra de remplacer les définitions de mise en page par défaut par page, afin que chaque page puisse avoir sa propre version de ce qui est fourni.

Création d'une hiérarchie parmi les routes.

Parfois, le modèle de mise en page peut cependant ne pas suffire. Au fur et à mesure que les applications deviennent de plus en plus complexes, il peut être nécessaire d'établir une relation fournisseur/consommateur entre les itinéraires. Une route enveloppera d'autres routes et leur fournira ainsi des définitions communes au lieu de dupliquer le code des développeurs. Dans cet esprit, il existe une Proposition d'emballage dans les discussions Next.js pour fournir une expérience de développement fluide pour y parvenir.

Pour le moment, il n'y a pas un faible -config solution pour ce modèle dans Next.js, mais à partir des exemples ci-dessus, nous pouvons trouver une solution. Prenez cet extrait directement à partir de la documentation  :

 importez la mise en page à partir de '../components/layout'
importer NestedLayout depuis '../components/nested-layout'

exporter la fonction par défaut Page() {
  revenir {
    /** Votre contenu */
  }
}

Page.getLayout = (page) => (
  
    {page}
  
)

Encore une fois le modèle getLayout ! Maintenant, il est fourni en tant que propriété de l'objet Page. Il prend un paramètre page tout comme un composant React prend le prop childrenet nous pouvons envelopper autant de couches que nous le souhaitons. Faites un résumé dans un module séparé et partagez cette logique avec certaines routes :

//routes/user-management.jsx

export const MainUserManagement = (page) => (
  
    
      {page}
    
  
)


// tableau de bord utilisateur.jsx
importer { MainUserManagement } depuis '../routes/user-management'

export const UserDashboard = (props) => (<>)

UserDashboard.getLayout = MainUserManagement

Growing Pains Strike Again : Provider Hell

Grâce à l'API contextuelle de React, nous avons échappé au Prop Drillingce qui était le problème que nous voulions résoudre. Nous avons maintenant un code lisible et nous pouvons transmettre des props à nos composants touchant uniquement les couches requises.

Finalement, notre application grandit et le nombre de props qui doivent être transmis augmente à un rythme de plus en plus rapide. Si nous prenons suffisamment de précautions pour isoler et éliminer les rendus inutiles, il est probable que nous rassemblons une quantité incalculable de à la racine de nos mises en page.

export const DefaultLayout = ({ children }) => {
  revenir (
    
      
        
          
            
              
                {enfants}
              
            
          
        
      
    
  )
}

C'est ce que nous appelons Provider Hell. Et cela peut empirer : et si SpecialProvider ne visait qu'un cas d'utilisation spécifique ? L'ajoutez-vous au moment de l'exécution ? L'ajout à la fois du fournisseur et du consommateur pendant l'exécution n'est pas vraiment simple.

Avec ce terrible problème, Jōtai a fait surface. C'est une bibliothèque de gestion d'état avec une signature très similaire à useState. Sous le capot, Jōtai utilise également l'API Context, mais il extrait l'enfer du fournisseur de notre code et propose même un mode « sans fournisseur » au cas où l'application ne nécessiterait qu'un seul magasin.

Grâce à l'approche ascendante, nous pouvons définir les atomes de Jōtai (la couche de données de chaque composant qui se connecte au magasin) dans un niveau de composant et la bibliothèque s'occupera de les lier au fournisseur. L'utilitaire de Jōtai comporte quelques fonctionnalités supplémentaires en plus du Context.Provider par défaut de React. Il isolera toujours les valeurs de chaque atome, mais il faudra une propriété initialValues pour déclarer un tableau de valeurs par défaut. Ainsi, l'exemple de Provider Hell ci-dessus ressemblerait à ceci :

import { Provider } from 'jotai'
importer {
  AuthAtom,
  UserAtom,
  ThèmeAtom,
  Atome spécial,
  Juste un autre atome,
  Atome très spécifique
} de '@atomes'
 
const VALEURS_DEFAUT = [
  [AuthAtom, 'value1'],
  [UserAtom, 'value2'],
  [ThemeAtom, 'value3'],
  [SpecialAtom, 'value4'],
  [JustAnotherAtom, 'value5'],
  [VerySpecificAtom, 'value6']
]

export const DefaultLayout = ({ enfants }) => {
  revenir (
    
      {enfants}
    
  )
}

Jōtai propose également d'autres approches pour composer et dériver facilement des définitions d'état les unes des autres. Il peut certainement résoudre les problèmes d'évolutivité de manière incrémentielle.

Récupération de l'état

Jusqu'à présent, nous avons créé des modèles et des exemples pour gérer l'état en interne dans l'application. Mais il ne faut pas être naïf, c'est rarement le cas où une application n'a pas besoin de récupérer du contenu ou des données à partir d'API externes. les données

  • incorporant des données dans l'état de l'application
  • Lorsque vous demandez des données côté client, il est important de faire attention à quelques éléments :

    1. la connexion réseau de l'utilisateur : évitez de récupérer des données déjà disponible
    2. que faire en attendant la réponse du serveur
    3. comment gérer lorsque les données ne sont pas disponibles (erreur de serveur ou pas de données)
    4. comment récupérer si l'intégration est interrompue (point de terminaison indisponible, ressource modifiée, etc.)

    Et c'est maintenant que les choses commencent à devenir intéressantes. Cette première puce, l'élément 1, est clairement liée à l'état de récupération, tandis que l'élément 2 passe lentement à l'état de gestion. Les éléments 3 et 4 appartiennent définitivement à la portée de l'état de gestion, mais ils dépendent tous deux de l'action d'extraction et de l'intégration du serveur. La ligne est définitivement floue. Traiter avec toutes ces pièces en mouvement est complexe, et ce sont des modèles qui ne changent pas beaucoup d'une application à l'autre. Chaque fois et quelle que soit la manière dont nous récupérons des données, nous devons gérer ces 4 scénarios.

    Heureusement, grâce à des bibliothèques telles que React-Query et SWR chaque modèle affiché pour l'état local est appliqué en douceur pour les données externes. Les bibliothèques comme celles-ci gèrent le cache localement, donc chaque fois que l'état est déjà disponible, elles peuvent tirer parti de la définition des paramètres pour renouveler les données ou utiliser à partir du cache local. De plus, ils peuvent même fournir à l'utilisateur des données obsolètes tandis qu' ils actualisent le contenu et demandent une mise à jour de l'interface chaque fois que possible.

    En plus de cela, l'équipe React a été transparente dès le début à propos de API à venir qui visent à améliorer l'expérience utilisateur et développeur sur ce front (consultez la documentation Suspense proposée ici). Grâce à cela, les auteurs de bibliothèques se sont préparés à l'arrivée de telles API et les développeurs peuvent commencer à travailler avec une syntaxe similaire à partir d'aujourd'hui.

    Alors maintenant, ajoutons un état externe à notre mise en page MainUserManagement avec SWR :

     importer { useSWR } à partir de 'swr'
    importer { UserInfoProvider } depuis '../context/user-info'
    importer { ExtDataProvider } de '../context/external-data-provider'
    importer { UserNavigationLayout } depuis '../layouts/user-navigation'
    importer { ErrorReporter } de '../components/error-reporter'
    importer { Chargement } depuis '../components/loading'
    
    export const MainUserManagement = (page) => {
      const { données, erreur } = useSWR('/api/endpoint')
    
      si (erreur) => 
      si (!données) => 
    
      revenir (
        
          
            
              {page}
            
          
        
      )
    }
    

    Comme vous pouvez le voir ci-dessus, le hook useSWR fournit de nombreuses abstractions :

    • a default fetcher
    • zero-config caching layer
    • error handler
    • loading handler[19659019]Avec 2 conditions, nous pouvons fournir des retours anticipés au sein de notre composant lorsque la demande échoue (erreur) ou lorsque l'aller-retour vers le serveur n'est pas encore terminé (chargement). Pour ces raisons, les bibliothèques sont proches des bibliothèques de gestion de l'État. Bien qu'ils ne soient pas exactement la gestion des utilisateurs, ils s'intègrent bien et nous fournissent suffisamment d'outils pour simplifier la gestion de ces états asynchrones complexes. le côté back-end. L'ajout de requêtes supplémentaires à votre application une fois qu'elle est déjà côté client affectera les performances perçues. Il existe un excellent article (et e-book !) getServerSideProps sur les applications Next.js. C'est encore un autre outil dans la ceinture du développeur avec lequel construire lorsqu'il est confronté à des situations particulières. vous n'êtes pas attentif lorsque vous les mettez en œuvre. Tout d'abord, récapitulons ce que nous avons couvert dans cet article :

      • Contexte pour éviter le forage des accessoires ;
      • React core APIs for management state (useState et useReducer );
      • Passer l'état côté client dans une application Next.js ;
      • Comment empêcher certaines routes d'accéder à l'état ;
      • Comment gérer la récupération de données côté client pour les applications Next.js.[19659019]Il y a trois compromis importants dont nous devons être conscients lorsque nous optons pour ces techniques :

        1. L'utilisation des méthodes côté serveur pour générer du contenu de manière statique est souvent préférable à la récupération de l'état côté client.
        2. Le L'API de contexte peut conduire à plusieurs rendus si vous ne faites pas attention à l'endroit où les changements d'état ont lieu. L'application React reste utile sur une application Next.js. La couche serveur peut être en mesure d'améliorer les performances, ce qui en soi peut atténuer certains problèmes de calcul. Mais il bénéficiera également du respect des meilleures pratiques courantes en matière de performances de rendu sur les applications.

          Essayez-le vous-même

          Vous pouvez vérifier les modèles décrits dans cet article en direct sur nextjs-layout-state. netlify.app ou consultez le code sur github.com/atilafassina/nextjs-layout-state. Vous pouvez même simplement cliquer sur ce bouton pour le cloner instantanément sur le fournisseur Git de votre choix et le déployer sur Netlify :

          Déployer sur Netlify

          Au cas où vous voudriez quelque chose de moins opiniâtre ou envisagez simplement de commencer avec Next.js, il y a ce projet de démarrage génial pour vous permettre de démarrer tout configuré pour un déploiement facile sur Netlify. Encore une fois, Netlify permet de le cloner facilement dans votre propre référentiel et de le déployer :

          Déployer sur Netlify

          Références

          Smashing Editorial" width="35" height="46" loading="lazy" decoding="async(vf, il)




    Source link