Maison > interface Web > js tutoriel > le corps du texte

Next.js Deep Dive : Créer une application Notes avec des fonctionnalités avancées

DDD
Libérer: 2024-11-03 15:07:03
original
1032 Les gens l'ont consulté

Next.js Deep Dive: Building a Notes App with Advanced Features## Introduction et objectifs

Dans cet article de blog, j'aimerais passer en revue les fonctionnalités Next.js les plus importantes dont vous aurez besoin dans des scénarios pratiques.

J'ai créé cet article de blog comme référence unique pour moi-même et pour le lecteur intéressé. Au lieu d'avoir à parcourir toute la documentation nextjs. Je pense qu'il sera plus facile d'avoir un article de blog condensé avec toutes les fonctionnalités pratiques importantes suivantes que vous pourrez visiter périodiquement pour rafraîchir vos connaissances !

Nous passerons en revue ensemble les fonctionnalités ci-dessous tout en créant une application de notes en parallèle.

  • Routeur d'application

    • Composants du serveur
    • Composants clients
    • Routage imbriqué
    • Itinéraires dynamiques
  • Chargement et gestion des erreurs

  • Actions du serveur

    • Création et utilisation des actions du serveur
    • Intégrer les actions du serveur avec les composants clients
  • Récupération et mise en cache des données

    • Utiliser unstable_cache pour la mise en cache côté serveur 1. Revalidation du cache avec revalidateTag
    • Utilisation d'unstable_cache pour la mise en cache côté serveur
  • Streaming et Suspense

    • Diffusion au niveau de la page avec chargement.tsx
    • Diffusion au niveau des composants à l'aide de Suspense
  • Itinéraires parallèles

    • Création et utilisation d'emplacements nommés
    • Implémentation du rendu simultané de plusieurs composants de pages
  • Gestion des erreurs

    • Implémentation des limites d'erreur avec error.tsx

Nos notes finales prenant le code d'application ressembleront à ceci :

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

N'hésitez pas à passer directement au code final que vous pouvez trouver dans ce spithacode du référentiel Github.

Alors sans plus attendre, commençons !

Next.js Deep Dive: Building a Notes App with Advanced Features

Concepts clés

Avant de plonger dans le développement de notre application Notes, j'aimerais présenter quelques concepts clés de Nextjs qu'il est important de connaître avant d'aller de l'avant.

Routeur d'application

Le App Router est un nouveau répertoire "/app" qui prend en charge de nombreuses choses qui n'étaient pas possibles dans l'ancien répertoire "/page" telles que :

  1. Composants du serveur.
  2. Mises en page partagées : fichier layout.tsx.
  3. Routage imbriqué : vous pouvez imbriquer des dossiers les uns dans les autres. L’URL du chemin de la page suivra la même imbrication de dossier. Par exemple, l'url correspondante de cette page imbriquée /app/notes/[noteId]/edit/page.tsx après avoir supposé que le paramètre dynamique [noteId] est égal à "1" est "/notes/1/edit.

  4. /loading.tsx fichier qui exporte un composant qui est rendu lorsqu'une page est diffusée vers le navigateur de l'utilisateur.

  5. /error.tsx fichier qui exporte un composant qui est rendu lorsqu'une page génère une erreur non détectée.

  6. Routage parallèle et de nombreuses fonctionnalités que nous allons parcourir lors de la création de notre application de notes.

Composants serveur vs composants clients

Plongeons dans un sujet vraiment important que tout le monde devrait maîtriser avant même de toucher Nextjs /app Router.

Composants du serveur

Un composant serveur est essentiellement un composant qui est rendu sur le serveur.

Tout composant qui n'est pas précédé de la directive "use client" est par défaut un composant serveur incluant les pages et les mises en page.

Les composants du serveur peuvent interagir avec n'importe quelle API nodejs ou n'importe quel composant destiné à être utilisé sur le serveur.

Il est possible de faire précéder les composants serveur du mot-clé async contrairement aux composants clients. Vous pouvez donc appeler n'importe quelle fonction asynchrone et l'attendre avant de restituer le composant.

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Vous vous demandez peut-être pourquoi pré-afficher les composants sur le serveur ?

La réponse peut être résumée en quelques mots SEO, performances et expérience utilisateur.

Lorsque l'utilisateur visite une page, le navigateur télécharge les éléments du site Web, notamment le HTML, le CSS et le javascript.

Le bundle javascript (qui inclut votre code framework) prend plus de temps à charger que le reste des ressources en raison de sa taille.

  • L'utilisateur devra donc attendre de voir quelque chose à l'écran.

  • La même chose s'applique pour les crawlers qui sont chargés de l'indexation de votre site Web.

  • De nombreuses autres métriques SEO telles que le LCP, le TTFB, le Taux de rebond,... seront affectées.

Composants clients

Un composant client est simplement un composant qui est livré au navigateur de l'utilisateur.

Les composants clients ne sont pas de simples composants HTML et CSS. Ils ont besoin d'interactivité pour fonctionner donc il n'est pas vraiment possible de les rendre sur le serveur.

L'interactivité est assurée soit par un framework javascript comme React (useState, useEffect), soit par un navigateur uniquement, soit par des API DOM.

Une déclaration de composant client doit être précédée de la directive "use client". Ce qui indique à Nextjs d'en ignorer la partie interactive (useState,useEffect...) et de l'envoyer directement au navigateur de l'utilisateur.

/client-component.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Différentes permutations de composabilité.

Je sais, les choses les plus frustrantes dans Nextjs, ce sont ces bugs étranges que vous pouvez rencontrer si vous avez manqué les règles d'imbrication entre les Composants serveur et les Composants client.

Donc, dans la section suivante, nous clarifierons cela en présentant les différentes permutations d'imbrication possibles entre les Composants serveur et les Composants client.

Nous allons ignorer ces deux permutations car elles sont évidemment autorisées : Composant Client dans un autre Composant Client et Composant Serveur à l'intérieur d'un autre Composant Serveur.

Rendu d'un composant serveur en tant qu'enfant d'un composant client

Vous pouvez importer des Composants client et les restituer normalement dans le composant serveur. Cette permutation est assez évidente car les pages et les mises en page sont par défaut des composants du serveur.

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Rendu d'un composant serveur en tant qu'enfant d'un composant client

Imaginez envoyer un composant client au navigateur de l'utilisateur, puis attendre que le composant serveur qui se trouve à l'intérieur de celui-ci restitue et récupère les données. Ce n'est pas possible car le composant serveur est déjà envoyé au client, comment pouvez-vous ensuite le restituer sur le serveur ?

C'est pourquoi ce type de permutation n'est pas pris en charge par Nextjs.

N'oubliez donc jamais d'éviter d'importer des Composants serveur à l'intérieur des Composants client pour les afficher en tant qu'enfants.

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Essayez toujours de réduire le javascript envoyé au navigateur de l'utilisateur en poussant les composants clients vers le bas dans l'arborescence jsx.

Une solution de contournement pour le rendu des composants du serveur à l'intérieur des composants clients

Il n'est pas possible d'importer et de restituer directement un Composant serveur en tant qu'enfant d'un Composant client mais il existe une solution de contournement qui utilise la nature de composabilité de réaction.

L'astuce consiste à transmettre le Composant serveur en tant qu'enfant du Composant client à un composant serveur de niveau supérieur ( ParentServerComponent).

Appelons ça le Trick de Papa :D.

Cette astuce garantit que le Composant serveur transmis est rendu sur le serveur avant d'envoyer le Composant client au navigateur de l'utilisateur.

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous verrons un exemple concret sur la /app/page.tsx page d'accueil de notre application notes.

Où nous allons rendre un composant serveur passé en tant qu'enfant à l'intérieur d'un composant client. Le composant client peut afficher ou masquer de manière conditionnelle le contenu rendu du composant serveur en fonction d'une valeur de variable d'état booléenne.

Action du serveur

Actions du serveur est une fonctionnalité nextjs intéressante qui permet d'appeler à distance et en toute sécurité une fonction qui est déclarée sur le serveur à partir de vos composants côté client .

Pour déclarer une action serveur il vous suffit d'ajouter la directive "use server" dans le corps de la fonction comme indiqué ci-dessous.

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La directive "use server" indique à Nextjs que la fonction contient du code côté serveur qui s'exécute uniquement sur le serveur.

Sous le capot, Nextjs envoie l'ID d'action et crée un point de terminaison réservé pour cette action.

Ainsi, lorsque vous appelez cette action dans un composant client Nextjs effectuera une requête POST au point de terminaison unique de l'action identifié par l'Action Id tout en transmettant le arguments sérialisés que vous avez transmis lors de l'appel de l'action dans le corps de la requête.

Mieux vaut clarifier cela avec cet exemple simplifié.

Nous avons vu précédemment, qu'il fallait utiliser le "use server" dans la directive du corps de la fonction pour déclarer une action du serveur. Mais que se passe-t-il si vous devez déclarer plusieurs actions du serveur à la fois.

Eh bien, vous pouvez simplement utiliser la directive en-tête ou au début d'un fichier comme indiqué dans le code ci-dessous.

/server/actions.ts

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Notez que l'action du serveur doit toujours être marquée comme asynchrone

  • Donc, dans le code ci-dessus, nous avons déclaré une action serveur nommée createLogAction.

  • L'action est chargée de sauvegarder une entrée de journal dans un fichier spécifique sur le serveur sous le répertoire /logs.

  • Le fichier est nommé en fonction de l'argument d'action name.

  • L'action Ajoute une entrée de journal qui comprend la date de création et l'argument de l'action message.

Maintenant, utilisons notre action créée dans le composant côté client CreateLogButton.

/components/CreateLogButton.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant bouton déclare une variable d'état locale nommée isSubmitting qui est utilisée pour savoir si l'action est en cours d'exécution ou non. Lorsque l'action est en cours d'exécution, le texte du bouton passe de "Bouton de journalisation" à "Chargement...".

L'action serveur est appelée lorsque l'on clique sur le composant Log Button.

Configuration de la logique métier

Création de notre modèle Note

Tout d'abord, commençons par créer nos schémas et types de validation de notes.

Comme les modèles sont censés gérer la validation des données, nous utiliserons à cet effet une bibliothèque populaire appelée zod.

Ce qui est cool avec zod, c'est son API descriptive facile à comprendre qui fait de la définition du modèle et de la génération du TypeScript correspondant une tâche transparente.

Nous n'utiliserons pas de modèle complexe et sophistiqué pour nos notes. Chaque note aura un identifiant unique, un titre, un contenu et un champ de date de création.

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous déclarons également quelques schémas supplémentaires utiles comme InsertNoteSchema et WhereNoteSchema qui nous faciliteront la vie lorsque nous créerons nos fonctions réutilisables qui manipuleront notre modèle plus tard.

Création d'une base de données simple en mémoire

Nous stockerons et manipulerons nos notes en mémoire.

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous stockons notre tableau notes dans l'objet global this pour éviter de perdre l'état de notre tableau à chaque fois que la constante notes est importée dans un fichier (rechargement de page...).

Création de nos cas d'utilisation d'applications

Créer un cas d'utilisation de note

Le cas d'utilisation createNote nous permettra d'insérer une note dans le tableau notes. Considérez la méthode notes.unshift comme l'inverse de la méthode notes.push car elle pousse l'élément au début du tableau au lieu de la fin de celui-ci.

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Cas d'utilisation de la note de mise à jour

Nous utiliserons updateNote pour mettre à jour une note spécifique dans le tableau notes en fonction de son identifiant. Il trouve d'abord l'index des éléments, renvoie une erreur s'il n'est pas trouvé et renvoie la note correspondante en fonction de l'index trouvé.

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Cas d'utilisation de suppression de note

La fonction de cas d'utilisation deleteNote sera utilisée pour supprimer une note donnée en fonction de l'identifiant de la note.
La méthode fonctionne de la même manière, elle trouve d'abord l'index de la note en fonction de son identifiant, renvoie une erreur si elle n'est pas trouvée puis renvoie la note correspondante indexée par l'identifiant trouvé.

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Obtenir un cas d'utilisation de notes

La fonction getNote est explicite, elle trouvera simplement une note compte tenu de son identifiant.

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Cas d'utilisation d'obtention de notes

Comme nous ne voulons pas déplacer l'intégralité de notre base de données de notes côté client, nous ne récupérerons qu'une partie du total des notes disponibles. Nous devons donc implémenter une pagination côté serveur.

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Donc, la fonction getNotes nous permettra essentiellement de récupérer une page spécifique de notre serveur en passant l'argument page.
L'argument limit sert à déterminer le nombre d'éléments qui sont présents sur une page donnée.

Par exemple :
Si le tableau notes contient 100 éléments et que l'argument limit est égal à 10.

En demandant la page 1 à notre serveur seuls les 10 premiers éléments seront retournés.

L'argument search sera utilisé pour implémenter la recherche côté serveur. Il indiquera au serveur de renvoyer uniquement les notes qui ont la chaîne search comme sous-chaîne soit dans le titre, soit dans les attributs de contenu.

Cas d'utilisation du résumé des notes

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Obtenir un cas d'utilisation d'activité récente

Ce cas d'utilisation sera utilisé pour obtenir de fausses données sur les activités récentes des utilisateurs.

Nous utiliserons cette fonction dans la page /dashboard.

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Cas d'utilisation d'obtention de balises récentes

Cette fonction de cas d'utilisation sera chargée d'obtenir des statistiques sur les différentes balises utilisées dans nos notes (#something).

Nous utiliserons cette fonction dans la page /dashboard.

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Cas d'utilisation d'obtention d'informations utilisateur

Nous utiliserons cette fonction de cas d'utilisation pour simplement renvoyer de fausses données sur certaines informations utilisateur telles que le nom, l'e-mail...

Nous utiliserons cette fonction dans la page /dashboard.

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Obtenir un cas d'utilisation de notes aléatoires

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Action du serveur de routeur d'applications et mise en cache

Page d'accueil (démo de la solution de contournement du composant serveur à l'intérieur du composant client)

Dans cette page d'accueil, nous présenterons l'astuce ou la solution de contournement précédente pour rendre un Composant serveur à l'intérieur d'un Composant client (L'astuce PaPa :D) .

/app/page.tsx

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans le code ci-dessus, nous déclarons un Composant serveur parent appelé Accueil qui est responsable du rendu de la page "/" dans notre application.

Nous importons un Composant Serveur nommé RandomNote et un ClientComponent nommé NoteOfTheDay.

Nous transmettons le composant serveur RandomNote en tant qu'enfant au composant côté client NoteOfTheDay.

/app/components/RandomNote.ts

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant serveur RandomNote fonctionne comme suit :

  • il récupère une note aléatoire à l'aide de la fonction de cas d'utilisation getRandomNote.

  • il restitue les détails de la note qui se composent du titre et d'une partie ou sous-chaîne de la note complète contenu.

/app/components/NoteOfTheDay.ts

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant client NoteOfTheDay de l'autre côté fonctionne comme décrit ci-dessous :

  • Il prend le prop children comme entrée (qui sera notre composant serveur RandomNote dans notre cas), puis le restitue de manière conditionnelle en fonction de la valeur de la variable d'état booléenne isVisible.
  • Le composant affiche également un bouton auquel est attaché un écouteur d'événement onClick, pour basculer la valeur de l'état de visibilité.

Page de remarques

/app/notes/page.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous allons commencer par créer la page /app/notes/page.tsx qui est un composant serveur chargé de :

  1. Obtention des paramètres de recherche de page qui sont les chaînes attachées à la fin de l'URL après la marque ? : http://localhost:3000/notes?page=1&search=Something

  2. Passer les paramètres de recherche dans une fonction déclarée localement appelée fetchNotes.

  3. La fonction fetchNotes utilise notre fonction de cas d'utilisation précédemment déclarée getNotes pour récupérer la page de notes actuelle.

  4. Vous pouvez remarquer que nous enveloppons la fonction getNotes avec une fonction utilitaire importée de "next/cache" appelée unstable_cache. La fonction de cache instable est utilisée pour mettre en cache la réponse de la fonction getNotes.

Si nous sommes sûrs qu'aucune note n'est ajoutée à la base de données. Cela n'a aucun sens de cliquer dessus à chaque fois que la page est rechargée. Ainsi, la fonction unstable_cache marque le résultat de la fonction getNotes avec la balise "notes" que nous pourrons utiliser plus tard pour invalider les "notes" mettre en cache si une note est ajoutée ou supprimée.

  1. La fonction fetchNotes renvoie deux valeurs : les notes et le total.

  2. Les données résultantes (notes et total) sont transmises à un Composant côté client appelé NotesList qui est responsable du rendu de nos notes.

Lorsque l'utilisateur clique sur Actualiser. Une page vierge apparaîtra à l'utilisateur pendant la récupération de nos données de notes.
Pour résoudre ce problème, nous utiliserons une fonctionnalité géniale de Nextjs appelée. Diffusion de pages côté serveur.

Nous pouvons le faire en créant un fichier loading.tsx, à côté de notre fichier /app/notes/page.tsx.

/app/notes/loading.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Pendant que la page est diffusée depuis le serveur, l'utilisateur verra une page de chargement squelette, qui donne à l'utilisateur une idée du type de contenu à venir.

Next.js Deep Dive: Building a Notes App with Advanced Features

N'est-ce pas cool :). créez simplement un fichier chargement.tsx et voilà, vous avez terminé. Votre ux s'épanouit jusqu'au niveau supérieur.

/app/notes/components/NotesList.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La liste de notes Composant côté client Reçoit les notes et les données liées à la pagination de son Composant serveur parent qui est la Page Notes.

Ensuite, le composant gère le rendu de la page de notes actuelle. Chaque carte de note individuelle est rendue à l'aide du composant NoteView.

Il fournit également des liens vers la page précédente et suivante à l'aide du composant Next.js Link qui est essentiel pour pré-récupérer les données de la page suivante et précédente afin de nous permettre d'avoir un client transparent et rapide -navigation latérale.

Pour gérer la Recherche côté serveur, nous utilisons un hook personnalisé appelé useNotesSearch qui gère essentiellement le déclenchement d'une récupération de notes lorsqu'un utilisateur tape une requête spécifique dans la recherche Input.

/app/notes/components/NoteView.ts

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant NoteView est simple, il est uniquement responsable du rendu de chaque carte de note individuelle avec son correspondant : le titre, une partie du contenu et des liens d'action pour afficher les détails de la note ou pour la modifier.

/app/notes/components/hooks/use-notes-search.ts

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le hook personnalisé useNotesSearch fonctionne comme suit :

  1. Il stocke le prop initialSearch dans un état local à l'aide du hook useState.

  2. Nous utilisons le hook useEffect React pour déclencher une navigation de page chaque fois que les valeurs des variables currentPage ou debouncingSearchValue changent.

  3. La nouvelle URL de la page est construite en tenant compte de la page actuelle et des valeurs de recherche.

  4. La fonction setSearch sera appelée chaque fois qu'un caractère change lorsque l'utilisateur tape quelque chose dans l'entrée de recherche. Cela entraînera trop de navigations en peu de temps.

  5. Pour éviter que nous déclenchions la navigation uniquement lorsque l'utilisateur arrête de taper d'autres termes, nous faisons rebondir la valeur de recherche pendant une durée spécifique (300 ms dans notre cas).

Créer une note

Ensuite, passons en revue le /app/notes/create/page.tsx qui est un wrapper de composant serveur autour du composant client CreateNoteForm.

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/create/page.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

/app/notes/create/components/CreateNoteForm.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le formulaire du composant client CreateNoteForm se charge de récupérer les données de l'utilisateur puis de les stocker dans des variables d'état locales (titre, contenu).

Lorsque le formulaire est soumis après avoir cliqué sur le bouton de soumission, le createNoteAction est soumis avec le titre et le contenu arguments d'état local. .

La variable booléenne d'état isSubmitting est utilisée pour suivre l'état de soumission de l'action.

Si le createNoteAction est soumis avec succès sans aucune erreur, nous redirigeons l'utilisateur vers la page /notes.

/app/notes/create/actions/create-note.action.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le code de l'action createNoteAction est simple, le fichier contenant est précédé de la directive "use server" indiquant à Next.js que cette action est appelable dans les composants clients.

Un point que nous devons souligner à propos des actions du serveur est que seule l'interface de l'action est envoyée au client, mais pas le code contenu dans l'action elle-même.

En d'autres termes, le code à l'intérieur de l'action vivra sur le serveur, nous ne devons donc faire confiance à aucune entrée provenant du client vers notre serveur.

C'est pourquoi nous utilisons zod ici pour valider l'argument d'action rawNote en utilisant notre schéma créé précédemment.

Après avoir validé nos entrées, nous appelons le cas d'utilisation createNote avec les données validées.

Si la note est créée avec succès, la fonction revalidateTag est appelée pour invalider l'entrée de cache qui est étiquetée comme "notes" (rappelez-vous la fonction unstable_cache qui est utilisé dans la page /notes).

Page de détails des notes

La page de détails des notes affiche le titre et le contenu complet d'une note spécifique en fonction de son identifiant unique. En plus de cela, il affiche quelques boutons d'action pour modifier ou supprimer la note.

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/[noteId]/page.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Nous récupérons d’abord les paramètres de la page à partir des accessoires de la page. Dans Next.js 13, nous devons attendre l'argument de la page params car c'est une promesse.

  2. Après cela, nous transmettons le params.noteId à la fonction fetchNote déclarée localement.

/app/notes/[noteId]/fetchers/fetch-note.ts

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. La fonction fetchNote enveloppe notre cas d'utilisation getNote avec le unstable_cache tout en marquant le résultat renvoyé avec "note-details" et note-details/${id} Balises.

  2. La balise "note-details" peut être utilisée pour invalider toutes les entrées du cache des détails de la note à la fois.

  3. En revanche, la balise note-details/${id} est associée uniquement à une note spécifique définie par son identifiant unique. Nous pouvons donc l'utiliser pour invalider l'entrée dans le cache d'une note spécifique au lieu de l'ensemble des notes.

/app/notes/[noteId]/loading.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Rappel

Le loading.tsx est une page Next.js spéciale qui est rendue pendant que la page de détails de la note récupère ses données sur le serveur.

Ou en d'autres termes, pendant que la fonction fetchNote exécute une page squelette sera affichée à l'utilisateur au lieu d'un écran vide.

Cette fonctionnalité nextjs s'appelle Page Streaming. Il permet d'envoyer l'intégralité de la mise en page parent statique d'une page dynamique tout en diffusant progressivement son contenu.

Cela augmente les performances et l'expérience utilisateur en évitant de bloquer l'interface utilisateur pendant que le contenu dynamique d'une page est récupéré sur le serveur.

/app/notes/[noteId]/components/DeleteNoteButton.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Plongeons maintenant dans le composant côté client DeleteNoteButton.

Next.js Deep Dive: Building a Notes App with Advanced Features

Le composant est responsable du rendu d'un bouton de suppression et de l'exécution de la deleteNoteAction, puis de la redirection de l'utilisateur vers la page /notes lorsque l'action est exécutée avec succès.

Pour suivre l'état d'exécution de l'action, nous utilisons une variable d'état locale isDeleting.

/app/notes/[noteId]/actions/delete-note.action.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le code deleteNoteAction fonctionne comme suit :

  1. Il utilise zod pour analyser et valider les entrées d'action.
  2. Après nous être assurés que notre entrée est sûre, nous la transmettons à notre fonction de cas d'utilisation deleteNote.
  3. Lorsque l'action est exécutée avec succès, nous utilisons revalidateTag pour invalider à la fois les "notes" et les note-details/${where.id} cache entrées.

Modifier la page de notes

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/[noteId]/edit/page.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La page /app/notes/[noteId]/edit/page.tsx est un composant serveur qui obtient le paramètre noteId de la promesse params.

Ensuite, il récupère la note à l'aide de la fonction fetchNote.

Après une récupération réussie. Il transmet la note au composant côté client EditNoteForm.

/app/notes/[noteId]/edit/components/EditNoteForm.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant côté client EditNoteForm reçoit la note et affiche un formulaire qui permet à l'utilisateur de mettre à jour les détails de la note.

Les variables d'état locales title et content sont utilisées pour stocker leurs valeurs d'entrée ou de zone de texte correspondantes.

Lorsque le formulaire est soumis via le bouton Mettre à jour la note. Le updateNoteAction est appelé avec les valeurs title et content comme arguments.

La variable d'état isSubmitting est utilisée pour suivre l'état de soumission de l'action, permettant d'afficher un indicateur de chargement lorsque l'action est en cours d'exécution.

/app/notes/[noteId]/edit/actions/edit-note.action.ts

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

L'action updateNoteAction fonctionne comme suit :

  1. Les entrées d'action sont validées à l'aide de leurs schémas zod correspondants (WhereNoteSchema et InsertNoteSchema).
  2. Après cela, la fonction de cas d'utilisation updateNote est appelée avec les données analysées et validées.
  3. Après avoir mis à jour la note avec succès, nous revalidons les balises "notes" et note-details/${where.id}.

Page du tableau de bord (fonctionnalité de streaming au niveau des composants)

/app/dashboard/page.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La page /app/dashboard/page.tsx est divisée en composants côté serveur plus petits : NotesSummary, RecentActivity et TagCloud.

Chaque composant du serveur récupère ses propres données indépendamment.

Chaque composant du serveur est enveloppé dans une limite React Suspense.

Le rôle de la limite de suspense est d'afficher un composant de secours (un Squelette dans notre cas) lorsque le composant serveur enfant récupère ses propres données.

Ou en d'autres termes, la limite Suspense nous permet de différer ou de retarder le rendu de ses enfants jusqu'à ce qu'une condition soit remplie (les données à l'intérieur des enfants sont en cours de chargement).

Ainsi, l'utilisateur pourra voir la page comme une combinaison d'un tas de squelettes. Pendant que la réponse pour chaque composant individuel est diffusée par le serveur.

L'un des principaux avantages de cette approche est d'éviter de bloquer l'interface utilisateur si un ou plusieurs composants du serveur prennent plus de temps que l'autre.

Donc, si nous supposons que les temps de récupération individuels pour chaque composant sont répartis comme suit :

  1. NotesSummary prend 2 secondes à charger.
  2. RecentActivity prend 1 seconde à charger.
  3. TagCloud prend 3 secondes à charger.

Lorsque nous appuyons sur Actualiser, la première chose que nous verrons, ce sont 3 chargeurs squelettes.

Après 1 seconde, le composant recentActivity apparaîtra.
Après 2 secondes le NotesSummary suivra puis le TagCloud.

Donc, au lieu de faire attendre l'utilisateur 3 secondes avant de voir un contenu. Nous avons réduit ce temps de 2 secondes en affichant en premier la RecentActivity.

Cette approche de rendu incrémentiel se traduit par une meilleure expérience utilisateur et de meilleures performances.

Next.js Deep Dive: Building a Notes App with Advanced Features

Le code des composants du serveur individuels est mis en évidence ci-dessous.

/app/dashboard/components/RecentActivity.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant serveur RecentActivity récupère essentiellement les dernières activités à l'aide de la fonction de cas d'utilisation getRecentActivity et les restitue dans une liste non ordonnée.

/app/dashboard/components/TagCloud.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant côté serveur TagCloud récupère puis restitue tous les noms de balises qui ont été utilisés dans le contenu des notes avec leur nombre respectif.

/app/dashboard/components/NotesSummary.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le composant serveur NotesSummary restitue les informations récapitulatives après les avoir récupérées à l'aide de la fonction de cas d'utilisation getNoteSummary.

Page de profil (fonctionnalités d'itinéraires parallèles)

Passons maintenant à la page de profil où nous passerons en revue une fonctionnalité nextjs intéressante appelée Routes parallèles.

Les itinéraires parallèles nous permettent de simultanément ou conditionnellement restituer une ou plusieurs pages dans la même mise en page.

Dans notre exemple ci-dessous, nous allons afficher la page d'informations utilisateur et la page de notes utilisateur dans la même mise en page qui est /app/profile .

Vous pouvez créer des itinéraires parallèles en utilisant des emplacements nommés. Un emplacement nommé est déclaré exactement comme une sous-page mais le symbole @ doit précéder le nom du dossier contrairement aux pages ordinaires.

Par exemple, dans le dossier /app/profile/, nous allons créer deux emplacements nommés :

  1. /app/profile/@info pour la page d'informations utilisateur.
  2. /app/profile/@notes pour la page de notes de l'utilisateur.

Next.js Deep Dive: Building a Notes App with Advanced Features

Créons maintenant un fichier de mise en page /app/profile/layout.tsx qui définira la mise en page de notre page /profile.

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme vous pouvez le voir dans le code ci-dessus, nous avons maintenant accès aux paramètres info et notes qui contiennent le contenu des pages @info et @notes.

Ainsi, la page @info sera rendue à gauche et les @notes seront rendues à droite.

Le contenu de page.tsx (référencé par enfants) sera rendu en bas de page.

Page @info

/app/profile/@info/page.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La UserInfoPage est un composant serveur qui récupérera les informations utilisateur à l'aide de la fonction de cas d'utilisation getUserInfo.

Le squelette de secours ci-dessus sera envoyé au navigateur de l'utilisateur lorsque le composant récupère les données et est rendu sur le serveur (Server Side Streaming).

/app/profile/@info/loading.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Page @notes

La même chose s'applique au composant côté serveur LastNotesPage. il récupérera les données et les rendra sur le serveur pendant qu'une interface utilisateur squelette sera affichée à l'utilisateur

/app/profile/@notes/page.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

/app/profile/@notes/loading.tsx

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Page d'erreur

Explorons maintenant une fonctionnalité plutôt intéressante de Nextjs, la page error.tsx.

Next.js Deep Dive: Building a Notes App with Advanced Features

Lorsque vous déployez votre application en production, vous souhaiterez sûrement afficher une erreur conviviale lorsqu'une erreur non détectée est générée depuis l'une de vos pages.

C'est là qu'intervient le fichier error.tsx.

Créons d'abord un exemple de page qui génère une erreur non détectée après quelques secondes.

/app/error-page/page.tsx

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


Copier après la connexion
Copier après la connexion
Copier après la connexion

Lorsque la page est en veille ou en attente que la fonction de veille soit exécutée. La page de chargement ci-dessous sera présentée à l'utilisateur.

/app/error-page/loading.tsx

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Après quelques secondes, l'erreur sera générée et supprimera votre page :(.

Pour éviter cela nous allons créer le fichier error.tsx qui exporte un composant qui fera office de Boundary d'erreur pour le /app/error-page/page .tsx.

/app/error-page/error.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Conclusion

Dans ce guide, nous avons exploré les fonctionnalités clés de Next.js en créant une application de notes pratiques. Nous avons couvert :

  1. Routeur d'application avec composants serveur et client
  2. Chargement et gestion des erreurs
  3. Actions du serveur
  4. Récupération et mise en cache des données
  5. Streaming et Suspense
  6. Itinéraires parallèles
  7. Limites d'erreur

En appliquant ces concepts dans un projet réel, nous avons acquis une expérience pratique des puissantes capacités de Next.js. N'oubliez pas que la meilleure façon de consolider votre compréhension est la pratique.

Prochaines étapes

  • Explorez le code complet : github.com/spithacode/next-js-features-notes-app
  • Étendez l'application avec vos propres fonctionnalités
  • Restez à jour avec la documentation officielle Next.js

Si vous avez des questions ou souhaitez discuter davantage de quelque chose, n'hésitez pas à me contacter ici.

Bon codage !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal