Heim  >  Artikel  >  Web-Frontend  >  Next.js Deep Dive: Erstellen einer Notizen-App mit erweiterten Funktionen

Next.js Deep Dive: Erstellen einer Notizen-App mit erweiterten Funktionen

DDD
DDDOriginal
2024-11-03 15:07:031033Durchsuche

Next.js Deep Dive: Building a Notes App with Advanced Features## Einführung und Ziele

In diesem Blogartikel möchte ich auf die wichtigsten Next.js-Funktionen eingehen, die Sie in der Praxis benötigen.

Diesen Blogartikel habe ich als Einzelreferenz für mich selbst und den interessierten Leser erstellt. Anstatt die gesamte nextjs-Dokumentation durchgehen zu müssen. Ich denke, dass es einfacher sein wird, einen komprimierten Blog-Artikel mit allen wichtigen praktischen Funktionen von nextjs zu haben, den Sie regelmäßig besuchen können, um Ihr Wissen aufzufrischen!

Wir werden gemeinsam die folgenden Funktionen durchgehen und gleichzeitig eine Notizenanwendung erstellen.

  • App-Router

    • Serverkomponenten
    • Client-Komponenten
    • Verschachteltes Routing
    • Dynamische Routen
  • Laden und Fehlerbehandlung

  • Serveraktionen

    • Serveraktionen erstellen und verwenden
    • Integration von Serveraktionen mit Clientkomponenten
  • Datenabruf und Zwischenspeicherung

    • Unstable_cache für serverseitiges Caching verwenden 1. Cache mit revalidateTag erneut validieren
    • Unstable_cache für serverseitiges Caching verwenden
  • Streaming und Spannung

    • Streaming auf Seitenebene mit Loading.tsx
    • Streaming auf Komponentenebene mit Suspense
  • Parallele Routen

    • Benannte Slots erstellen und verwenden
    • Implementierung des gleichzeitigen Renderns mehrerer Seitenkomponenten
  • Fehlerbehandlung

    • Fehlergrenzen mit error.tsx implementieren

Unsere letzten Notizen zum Anwendungscode werden wie folgt aussehen:

- 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

Sie können gerne direkt zum endgültigen Code springen, den Sie in diesem Github-Repository-Spithacode finden.

Also ohne weitere Umschweife, fangen wir an!

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

Schlüsselkonzepte

Bevor ich mich mit der Entwicklung unserer Notizenanwendung befasse, möchte ich einige wichtige nextjs-Konzepte vorstellen, die Sie kennen sollten, bevor Sie fortfahren.

App-Router

Der App Router ist ein neues Verzeichnis "/app", das viele Dinge unterstützt, die im alten Verzeichnis "/page" nicht möglich waren, wie z :

  1. Serverkomponenten.
  2. Freigegebene Layouts: Datei „layout.tsx“.
  3. Verschachteltes Routing: Sie können Ordner ineinander verschachteln. Die Seitenpfad-URL folgt der gleichen Ordnerverschachtelung. Beispielsweise die entsprechende URL dieser verschachtelten Seite /app/notes/[noteId]/edit/page.tsx, nachdem angenommen wurde, dass der dynamische Parameter [noteId] gleich "1" ist "/notes/1/edit.

  4. /loading.tsx-Datei, die eine Komponente exportiert, die gerendert wird, wenn eine Seite an den Benutzerbrowser gestreamt wird.

  5. /error.tsx-Datei, die eine Komponente exportiert, die gerendert wird, wenn eine Seite einen nicht erfassten Fehler auslöst.

  6. Paralleles Routing und viele Funktionen, die wir beim Erstellen unserer Notizenanwendung nutzen werden.

Serverkomponenten vs. Clientkomponenten

Lassen Sie uns in ein wirklich wichtiges Thema eintauchen, das jeder beherrschen sollte, bevor er Nextjs

/app Router überhaupt berührt.

Serverkomponenten

Eine

Serverkomponente ist im Grunde eine Komponente, die auf dem Server gerendert wird.

Jede Komponente, der nicht die Anweisung

„use client“ vorangestellt ist, ist standardmäßig eine Serverkomponente, einschließlich Seiten und Layouts.

Serverkomponenten können mit jeder NodeJS-API oder jeder Komponente interagieren, die auf dem Server verwendet werden soll.

Im Gegensatz zu

Clientkomponenten ist es möglich, Serverkomponenten das Schlüsselwort async voranzustellen. Sie können also jede asynchrone Funktion aufrufen und darauf warten, bevor Sie die Komponente rendern.

- 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

Sie fragen sich vielleicht, warum Sie die Komponenten überhaupt auf dem Server vorrendern sollten?

Die Antwort lässt sich in wenigen Worten zusammenfassen:

SEO, Leistung und Benutzererfahrung.

Wenn der Benutzer eine Seite besucht, lädt der Browser die Website-Assets herunter, einschließlich HTML, CSS und Javascript.

Das Javascript-Bundle (das Ihren Framework-Code enthält) benötigt aufgrund seiner Größe mehr Zeit zum Laden als die übrigen Assets.

  • Der Benutzer muss also warten, bis er etwas auf dem Bildschirm sieht.

  • Das Gleiche gilt für die

    Crawler, die für die Indexierung Ihrer Website verantwortlich sind.

  • Viele andere

    SEO-Metriken wie LCP, TTFB, Absprungrate,... werden davon betroffen sein.

Client-Komponenten

Eine

Client-Komponente ist einfach eine Komponente, die an den Browser des Benutzers gesendet wird.

Client-Komponenten sind nicht nur bloße HTML- und CSS-Komponenten. Sie benötigen Interaktivität, um zu funktionieren, daher ist es nicht wirklich möglich, sie auf dem Server zu rendern.

Die

Interaktivität wird entweder durch ein Javascript-Framework wie React ( useState, useEffect) oder nur durch Browser oder DOM-APIs gewährleistet.

Vor einer Client-Komponenten-Deklaration sollte die Anweisung „use client“ stehen. Dadurch wird Nextjs angewiesen, den interaktiven Teil davon (useState, useEffect...) zu ignorieren und ihn direkt an den Browser des Benutzers zu senden.

/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

Verschiedene Zusammensetzbarkeitspermutationen.

Ich weiß, die frustrierendsten Dinge in Nextjs sind diese seltsamen Fehler, auf die man stoßen kann, wenn man die Regeln der Verschachtelung zwischen Serverkomponenten und Clientkomponenten nicht beachtet.

Im nächsten Abschnitt werden wir dies klären, indem wir die verschiedenen möglichen Verschachtelungspermutationen zwischen Serverkomponenten und Clientkomponenten vorstellen.

Wir werden diese beiden Permutationen überspringen, da sie offensichtlich zulässig sind: Client-Komponente eine andere Client-Komponente und Server-Komponente innerhalb einer anderen Server-Komponente.

Rendern einer Serverkomponente als untergeordnetes Element einer Clientkomponente

Sie können Client-Komponenten importieren und sie normal innerhalb der Serverkomponente rendern. Diese Permutation ist ziemlich offensichtlich, da Seiten und Layouts standardmäßig Serverkomponenten sind.

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>
  )

}

Rendern einer Serverkomponente als untergeordnetes Element einer Clientkomponente

Stellen Sie sich vor, Sie senden eine Client-Komponente an den Browser des Benutzers und warten dann darauf, dass die darin befindliche Serverkomponente die Daten rendert und abruft. Das ist nicht möglich, da die Serverkomponente bereits an den Client gesendet wurde. Wie können Sie sie dann auf dem Server rendern?

Aus diesem Grund wird diese Art der Permutation von Nextjs nicht unterstützt.

Denken Sie also immer daran, Serverkomponenten nicht in Clientkomponenten zu importieren, um sie als untergeordnete Elemente darzustellen.

"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 ...............*/}
</>
  )
}


Versuchen Sie immer, das an den Browser des Benutzers gesendete Javascript zu reduzieren, indem Sie die Client-Komponenten im jsx-Baum nach unten verschieben.

Eine Problemumgehung: Rendern von Serverkomponenten innerhalb von Clientkomponenten

Es ist nicht möglich, eine Serverkomponente direkt als untergeordnetes Element einer Clientkomponente zu importieren und zu rendern, aber es gibt eine Problemumgehung, die die React-Composability-Natur.

Der Trick besteht darin, die

Serverkomponente als untergeordnetes Element der Clientkomponente an einer Serverkomponente höherer Ebene ( ParentServerComponent) zu übergeben.

Nennen wir es den

Papa-Trick :D.

Dieser Trick stellt sicher, dass die übergebene

Serverkomponente auf dem Server gerendert wird, bevor die Clientkomponente an den Browser des Benutzers gesendet wird.

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Ein konkretes Beispiel sehen wir auf der Startseite

/app/page.tsx unserer Notizenanwendung.

Wo wir eine Serverkomponente rendern, die als untergeordnetes Element innerhalb einer Clientkomponente übergeben wird. Die Client-Komponente kann den gerenderten Inhalt der Server-Komponente abhängig von einem booleschen Statusvariablenwert bedingt anzeigen oder ausblenden.

Serveraktion

Serveraktionen ist eine interessante Funktion von nextjs, die es ermöglicht, remote und sicher eine Funktion aufzurufen, die auf dem Server von Ihren clientseitigen Komponenten aus deklariert wird .

Um eine Serveraktion zu deklarieren, müssen Sie lediglich die Anweisung "use server" in den Hauptteil der Funktion einfügen, wie unten gezeigt.

- 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

Die Anweisung "Server verwenden" teilt Nextjs mit, dass die Funktion serverseitigen Code enthält, der nur auf dem Server ausgeführt wird.

Unter der Haube versendet Nextjs die Aktions-ID und erstellt einen reservierten Endpunkt für diese Aktion.

Wenn Sie diese Aktion also in einer Client-Komponente aufrufen, führt Nextjs eine POST-Anfrage an den eindeutigen Endpunkt der Aktion durch, der durch die Aktions-ID identifiziert wird, während die übergeben wird Serialisierte Argumente, die Sie beim Aufruf der Aktion im Anfragetext übergeben haben.

Lassen Sie uns das anhand dieses vereinfachten Beispiels besser verdeutlichen.

Wir haben zuvor gesehen, dass Sie „use server“ in der Funktionskörperanweisung verwenden müssen, um eine Serveraktion zu deklarieren. Aber was wäre, wenn Sie eine Reihe von Serveraktionen gleichzeitig deklarieren müssten?

Nun, Sie können die Direktive einfach in der Kopfzeile oder am Anfang einer Datei verwenden, wie im Code unten gezeigt.

/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>
  )

}

Beachten Sie, dass die Serveraktion immer als asynchron markiert sein sollte

  • Also haben wir im obigen Code eine Serveraktion mit dem Namen createLogAction deklariert.

  • Die Aktion ist dafür verantwortlich, einen Protokolleintrag in einer bestimmten Datei auf dem Server im Verzeichnis /logs zu speichern.

  • Die Datei wird basierend auf dem Aktionsargument name benannt.

  • Die Aktion fügt einen Protokolleintrag hinzu, der aus dem Erstellungsdatum und dem Aktionsargument Nachricht besteht.

Nun nutzen wir unsere erstellte Aktion in der clientseitigen Komponente 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 ...............*/}
</>
  )
}


Die Schaltflächenkomponente deklariert eine lokale Statusvariable mit dem Namen isSubmitting, die verwendet wird, um zu verfolgen, ob die Aktion ausgeführt wird oder nicht. Wenn die Aktion ausgeführt wird, ändert sich der Schaltflächentext von „Log-Schaltfläche“ zu „Laden...“.

Die Serveraktion wird aufgerufen, wenn wir auf die Komponente Log-Schaltfläche klicken.

Einrichtung der Geschäftslogik

Erstellen unseres Note-Modells

Beginnen wir zunächst mit der Erstellung unserer Note-Validierungsschemata und -Typen.

Da Modelle die Datenvalidierung übernehmen sollen, verwenden wir zu diesem Zweck eine beliebte Bibliothek namens zod.

Das Coole an zod ist seine beschreibende, leicht verständliche API, die die Definition des Modells und die Generierung des entsprechenden TypeScript zu einer nahtlosen Aufgabe macht.

Wir werden für unsere Notizen kein kompliziertes Modell verwenden. Jede Notiz hat eine eindeutige ID, einen Titel, einen Inhalt und ein Feld für das Erstellungsdatum.

- 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

Wir deklarieren außerdem einige hilfreiche zusätzliche Schemata wie das InsertNoteSchema und das WhereNoteSchema, die uns das Leben erleichtern, wenn wir unsere wiederverwendbaren Funktionen erstellen, die unser Modell später manipulieren.

Erstellen einer einfachen In-Memory-Datenbank

Wir werden unsere Notizen im Gedächtnis speichern und bearbeiten.

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>
  )

}

Wir speichern unser Notizen-Array im globalen Objekt this, um zu vermeiden, dass der Status unseres Arrays jedes Mal verloren geht, wenn die Notizenkonstante in eine Datei importiert wird (Neuladen der Seite...).

Erstellen unserer Anwendungsfälle

Notiz-Anwendungsfall erstellen

Der Anwendungsfall createNote ermöglicht es uns, eine Notiz in das Notizen-Array einzufügen. Stellen Sie sich die Methode notes.unshift als Umkehrung der Methode notes.push vor, da sie das Element an den Anfang des Arrays und nicht an dessen Ende verschiebt.

"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 ...............*/}
</>
  )
}


Anwendungsfall „Aktualisierungshinweis“.

Wir werden updateNote verwenden, um eine bestimmte Notiz im Notizen-Array anhand ihrer ID zu aktualisieren. Es findet zunächst den Index der Elemente, gibt einen Fehler aus, wenn er nicht gefunden wird, und gibt die entsprechende Notiz basierend auf dem gefundenen Index zurück.

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Notiz-Anwendungsfall löschen

Die Anwendungsfallfunktion deleteNote wird verwendet, um eine bestimmte Notiz anhand der Notiz-ID zu löschen.
Die Methode funktioniert ähnlich: Zuerst findet sie den Index der Notiz anhand ihrer ID, gibt einen Fehler aus, wenn sie nicht gefunden wird, und gibt dann die entsprechende Notiz zurück, die durch die gefundene ID indiziert ist.

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

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

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Holen Sie sich einen Notiz-Anwendungsfall

Die Funktion getNote ist selbsterklärend, sie findet einfach eine Notiz anhand ihrer ID.

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


Holen Sie sich den Anwendungsfall „Notizen“.

Da wir nicht unsere gesamte Notizendatenbank auf die Clientseite übertragen möchten, holen wir nur einen Teil der insgesamt verfügbaren Notizen ab. Daher müssen wir eine serverseitige Paginierung implementieren.

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>
}

Die Funktion getNotes ermöglicht es uns also grundsätzlich, eine bestimmte Seite von unserem Server abzurufen, indem wir das Argument Seite übergeben.
Das Argument Limit dient dazu, die Anzahl der Elemente zu bestimmen, die auf einer bestimmten Seite vorhanden sind.

Zum Beispiel:
Wenn das Array notes 100 Elemente enthält und das Argument limit gleich 10 ist.

Beim Anfordern von Seite 1 von unserem Server werden nur die ersten 10 Artikel zurückgegeben.

Das Argument search wird verwendet, um die serverseitige Suche zu implementieren. Dadurch wird der Server angewiesen, nur Notizen zurückzugeben, die die Zeichenfolge Suche als Teilzeichenfolge enthalten, entweder im Titel oder in den Inhaltsattributen.

Holen Sie sich den Anwendungsfall „Notizenzusammenfassung“.

- 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

Rufen Sie den Anwendungsfall „Letzte Aktivität“ ab

Dieser Anwendungsfall wird verwendet, um gefälschte Daten über die letzten Aktivitäten der Benutzer zu erhalten.

Wir werden diese Funktion auf der Seite /dashboard verwenden.

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>
  )

}

Anwendungsfall „Aktuelle Tags abrufen“.

Diese Anwendungsfallfunktion ist dafür verantwortlich, Statistiken über die verschiedenen Tags zu erhalten, die in unseren Notizen verwendet werden (#something).

Wir werden diese Funktion auf der Seite /dashboard verwenden.

"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 ...............*/}
</>
  )
}


Anwendungsfall „Benutzerinformationen abrufen“.

Wir werden diese Anwendungsfallfunktion verwenden, um einfach einige gefälschte Daten über einige Benutzerinformationen wie den Namen, die E-Mail-Adresse usw. zurückzugeben.

Wir werden diese Funktion auf der Seite /dashboard verwenden.

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Holen Sie sich einen Anwendungsfall für zufällige Notizen

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

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

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Aktion und Caching des App Router-Servers

Startseite (Server-Komponente innerhalb der Client-Komponenten-Workaround-Demo)

Auf dieser Homepage demonstrieren wir den vorherigen Trick oder Workaround zum Rendern einer Serverkomponente innerhalb einer Clientkomponente (Der PaPa-Trick :D) .

/app/page.tsx

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


Im obigen Code deklarieren wir eine übergeordnete Serverkomponente namens Home, die für die Darstellung der Seite "/" in unserer Anwendung verantwortlich ist.

Wir importieren eine Serverkomponente mit dem Namen RandomNote und eine ClientKomponente mit dem Namen NoteOfTheDay.

Wir übergeben die Serverkomponente RandomNote als untergeordnete Komponente an die clientseitige Komponente 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>
}

Die Serverkomponente RandomNote funktioniert wie folgt:

  • Es ruft eine zufällige Notiz mithilfe der Anwendungsfallfunktion getRandomNote ab.

  • es stellt die Notizdetails dar, die aus dem Titel und einem Teil oder einer Unterzeichenfolge des vollständigen Inhalts der Notiz bestehen.

/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

Die NoteOfTheDay Client-Komponente auf der anderen Seite funktioniert wie unten beschrieben:

  • Es nimmt die untergeordnete Requisite als Eingabe (die in unserem Fall unsere RandomNote-Serverkomponente sein wird) und rendert sie dann bedingt abhängig vom Wert der booleschen Zustandsvariablen isVisible.
  • Die Komponente rendert außerdem eine Schaltfläche mit einem angehängten onClick-Ereignis-Listener, um den Sichtbarkeitsstatuswert umzuschalten.

Seite „Notizen“.

/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>
  )

}

Wir beginnen mit der Erstellung der Seite /app/notes/page.tsx, die eine Serverkomponente ist, die verantwortlich ist für:

  1. Abrufen der Seitensuchparameter, bei denen es sich um die am Ende der URL nach der Markierung ? angehängten Zeichenfolgen handelt: http://localhost:3000/notes?page=1&search=Something

  2. Übergabe der Suchparameter an eine lokal deklarierte Funktion namens fetchNotes.

  3. Die Funktion fetchNotes verwendet unsere zuvor deklarierte Anwendungsfallfunktion getNotes, um die aktuelle Notizenseite abzurufen.

  4. Sie können feststellen, dass wir die Funktion getNotes mit einer aus "next/cache" importierten Dienstprogrammfunktion namens unstable_cache umschließen. Die Funktion „Instabiler Cache“ wird verwendet, um die Antwort der Funktion „getNotes“ zwischenzuspeichern.

Wenn wir sicher sind, dass der Datenbank keine Notizen hinzugefügt werden. Es macht keinen Sinn, jedes Mal darauf zu klicken, wenn die Seite neu geladen wird. Die Funktion unstable_cache markiert also das Funktionsergebnis getNotes mit dem Tag "notes", das wir später verwenden können, um die "notes" Cache, wenn eine Notiz hinzugefügt oder gelöscht wird.

  1. Die Funktion

    fetchNotes gibt zwei Werte zurück: die Notizen und die Gesamtsumme.

  2. Die resultierenden Daten (Notizen und Gesamtmenge) werden an eine

    Client-seitige Komponente namens NotesList übergeben, die für die Darstellung unserer Notizen verantwortlich ist.

Wenn der Benutzer auf „Aktualisieren“ klickt. Dem Benutzer wird eine leere Seite angezeigt, während unsere Notizendaten abgerufen werden.

Um dieses Problem zu lösen, verwenden wir eine tolle Nextjs-Funktion namens.
Serverseitiges Seiten-Streaming.

Das können wir tun, indem wir eine

loading.tsx-Datei neben unserer /app/notes/page.tsx-Datei erstellen.

/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 ...............*/}
</>
  )
}


Während die Seite vom Server gestreamt wird, sieht der Benutzer eine Skelett-Ladeseite, die dem Benutzer eine Vorstellung von der Art des kommenden Inhalts gibt.

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

Ist das nicht cool :). Erstellen Sie einfach eine Datei „loading.tsx“ und voilà, Sie sind fertig. Ihr UX entwickelt sich auf die nächste Ebene.

/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

Die Notizenliste Clientseitige Komponente Empfängt die Notizen und Paginierungsdaten von ihrer übergeordneten Serverkomponente, der NotesPage.

Dann übernimmt die Komponente das Rendern der aktuellen Seite mit Notizen. Jede einzelne Notizkarte wird mit der Komponente NoteView gerendert.

Es stellt auch Links zur vorherigen und nächsten Seite mithilfe der Next.js-Komponente Link bereit, die wichtig ist, um die Daten der nächsten und vorherigen Seite vorab abzurufen, damit wir einen nahtlosen und schnellen Client haben -Seitennavigation.

Um die Serverseitige Suche zu handhaben, verwenden wir einen benutzerdefinierten Hook namens useNotesSearch, der im Wesentlichen das Auslösen eines erneuten Abrufs von Notizen übernimmt, wenn ein Benutzer eine bestimmte Abfrage in die Suche eingibt Eingabe.

/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>
  )

}

Die Komponente NoteView ist unkompliziert. Sie ist nur für die Darstellung jeder einzelnen Notizkarte mit dem entsprechenden Titel, einem Teil des Inhalts und Aktionslinks zum Anzeigen der Notizdetails oder zum Bearbeiten verantwortlich.

/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 ...............*/}
</>
  )
}


Der benutzerdefinierte Hook useNotesSearch funktioniert wie folgt:

  1. Es speichert die Requisite initialSearch in einem lokalen Zustand mithilfe des Hooks useState.

  2. Wir verwenden den React-Hook useEffect, um eine Seitennavigation auszulösen, wenn sich die Variablenwerte currentPage oder debouncedSearchValue ändern.

  3. Die neue Seiten-URL wird unter Berücksichtigung der aktuellen Seite und der Suchwerte erstellt.

  4. Die Funktion setSearch wird jedes Mal aufgerufen, wenn sich ein Zeichen ändert, wenn der Benutzer etwas in die Sucheingabe eingibt. Das führt in kurzer Zeit zu zu vielen Navigationen.

  5. Um zu vermeiden, dass wir die Navigation nur dann auslösen, wenn der Benutzer aufhört, andere Begriffe einzugeben, entprellen wir den Suchwert für eine bestimmte Zeitspanne (in unserem Fall 300 ms).

Notiz erstellen

Als nächstes gehen wir die /app/notes/create/page.tsx durch, die ein Serverkomponenten-Wrapper um die CreateNoteForm-Clientkomponente ist.

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/>
  </>

  )
}



/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

Das CreateNoteForm-Clientkomponentenformular ist dafür verantwortlich, die Daten vom Benutzer abzurufen und sie dann in lokalen Statusvariablen (Titel, Inhalt) zu speichern.

Wenn das Formular nach dem Klicken auf die Schaltfläche Senden gesendet wird, wird die createNoteAction mit den lokalen Statusargumenten title und content gesendet .

Die boolesche Statusvariable isSubmitting wird verwendet, um den Status der Aktionsübermittlung zu verfolgen.

Wenn die createNoteAction erfolgreich und ohne Fehler übermittelt wird, leiten wir den Benutzer zur Seite /notes weiter.

/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>
  )

}

Der Aktionscode createNoteAction ist unkompliziert. Der enthaltenden Datei wird die Anweisung "use server" vorangestellt, die Next.js anzeigt, dass diese Aktion in Clientkomponenten aufrufbar ist.

Ein Punkt, den wir bei Serveraktionen hervorheben sollten, ist, dass nur die Aktionsschnittstelle an den Client gesendet wird, nicht jedoch der Code innerhalb der Aktion selbst.

Mit anderen Worten: Der Code innerhalb der Aktion wird auf dem Server gespeichert, daher sollten wir keinen Eingaben vertrauen, die vom Client an unseren Server gesendet werden.

Deshalb verwenden wir hier zod, um das Aktionsargument rawNote mithilfe unseres zuvor erstellten Schemas zu validieren.

Nachdem wir unsere Eingaben validiert haben, rufen wir den Anwendungsfall createNote mit den validierten Daten auf.

Wenn die Notiz erfolgreich erstellt wurde, wird die Funktion revalidateTag aufgerufen, um den Cache-Eintrag ungültig zu machen, der als "notizen" gekennzeichnet ist (denken Sie an die Funktion unstable_cache). welches auf der Seite /Notizen verwendet wird).

Seite „Hinweisdetails“.

Auf der Seite mit den Notizdetails werden der Titel und der vollständige Inhalt einer bestimmten Notiz anhand ihrer eindeutigen ID angezeigt. Darüber hinaus werden einige Aktionsschaltflächen zum Bearbeiten oder Löschen der Notiz angezeigt.

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 ...............*/}
</>
  )
}


  1. Zuerst rufen wir die Seitenparameter aus den Seiten-Requisiten ab. In Next.js 13 müssen wir auf das Seitenargument params warten, weil es ein Versprechen ist.

  2. Danach übergeben wir die params.noteId an die lokal deklarierte Funktion fetchNote.

/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

  1. Die fetchNote-Funktion umschließt unseren getNote-Anwendungsfall mit dem unstable_cache und markiert das zurückgegebene Ergebnis mit "note-details" und note-details/${id} Tags.

  2. Das Tag "note-details" kann verwendet werden, um alle Notizdetails-Cache-Einträge auf einmal ungültig zu machen.

  3. Andererseits ist das Tag note-details/${id} nur einer bestimmten Notiz zugeordnet, die durch ihre eindeutige ID definiert ist. So können wir damit den Cache-Eintrag einer bestimmten Notiz ungültig machen, anstatt den gesamten Notizensatz.

/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>
  )

}

Erinnerung

Die loading.tsx ist eine spezielle Next.js-Seite, die gerendert wird, während die Seite mit den Notizdetails ihre Daten vom Server abruft.

Oder mit anderen Worten: Während die Funktion fetchNote ausführt, wird dem Benutzer eine Skelettseite anstelle eines leeren Bildschirms angezeigt.

Diese nextjs-Funktion heißt Seiten-Streaming. Es ermöglicht das Senden des gesamten statischen übergeordneten Layouts einer dynamischen Seite, während der Inhalt schrittweise gestreamt wird.

Dies erhöht die Leistung und das Benutzererlebnis, indem verhindert wird, dass die Benutzeroberfläche blockiert wird, während der dynamische Inhalt einer Seite auf dem Server abgerufen wird.

/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 ...............*/}
</>
  )
}


Lassen Sie uns nun in die clientseitige Komponente DeleteNoteButton eintauchen.

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

Die Komponente ist dafür verantwortlich, eine Löschschaltfläche darzustellen und die deleteNoteAction auszuführen. Anschließend wird der Benutzer zur Seite /notes umgeleitet, wenn die Aktion erfolgreich ausgeführt wurde.

Um den Status der Aktionsausführung zu verfolgen, verwenden wir eine lokale Statusvariable isDeleting.

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

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Der deleteNoteAction-Code funktioniert wie folgt:

  1. Zod wird verwendet, um die Aktionseingaben zu analysieren und zu validieren.
  2. Nachdem wir sichergestellt haben, dass unsere Eingabe sicher ist, übergeben wir sie an unsere Anwendungsfallfunktion deleteNote.
  3. Wenn die Aktion erfolgreich ausgeführt wird, verwenden wir revalidateTag, um sowohl den "notes" als auch den note-details/${where.id}-Cache ungültig zu machen Einträge.

Notizseite bearbeiten

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

Die Seite /app/notes/[noteId]/edit/page.tsx ist eine Serverkomponente, die den noteId-Parameter aus dem Parameterversprechen erhält.

Dann wird die Notiz mit der Funktion fetchNote abgerufen.

Nach einem erfolgreichen Abruf. Die Notiz wird an die clientseitige Komponente EditNoteForm übergeben.

/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>
  )

}

Die clientseitige Komponente EditNoteForm empfängt die Notiz und rendert ein Formular, das es dem Benutzer ermöglicht, die Details der Notiz zu aktualisieren.

Die lokalen Statusvariablen Titel und Inhalt werden zum Speichern ihrer entsprechenden Eingabe- oder Textbereichswerte verwendet.

Wenn das Formular über die Schaltfläche Hinweis aktualisieren übermittelt wird. Die updateNoteAction wird mit den Werten title und content als Argumente aufgerufen.

Die Statusvariable isSubmitting wird verwendet, um den Status der Aktionsübermittlung zu verfolgen und ermöglicht die Anzeige eines Ladeindikators, wenn die Aktion ausgeführt wird.

/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 ...............*/}
</>
  )
}


Die Aktion updateNoteAction funktioniert wie folgt:

  1. Die Aktionseingaben werden mithilfe ihrer entsprechenden Zod-Schemas validiert (WhereNoteSchema und InsertNoteSchema).
  2. Danach wird die Anwendungsfallfunktion updateNote mit den analysierten und validierten Daten aufgerufen.
  3. Nach erfolgreicher Aktualisierung der Notiz validieren wir die Tags "notes" und note-details/${where.id} erneut.

Dashboard-Seite (Streaming-Funktion auf Komponentenebene)

/app/dashboard/page.tsx

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



Die Seite /app/dashboard/page.tsx ist in kleinere serverseitige Komponenten unterteilt: NotesSummary, RecentActivity und TagCloud.

Jede Serverkomponente ruft ihre eigenen Daten unabhängig ab.

Jede Serverkomponente ist in eine React Suspense-Grenze eingeschlossen.

Die Aufgabe der Suspense-Grenze besteht darin, eine Fallback-Komponente (in unserem Fall ein Skelett) anzuzeigen, wenn die untergeordnete Serverkomponente ihre eigenen Daten abruft.

Oder mit anderen Worten: Die Suspense-Grenze ermöglicht es uns, die Darstellung ihrer untergeordneten Elemente zu verschieben oder zu verzögern, bis eine Bedingung erfüllt ist (die Daten in den untergeordneten Elementen werden geladen).

So kann der Benutzer die Seite als eine Kombination aus einer Reihe von Skeletten sehen. Während die Antwort für jede einzelne Komponente vom Server gestreamt wird.

Ein wesentlicher Vorteil dieses Ansatzes besteht darin, ein Blockieren der Benutzeroberfläche zu vermeiden, wenn eine oder mehrere der Serverkomponenten im Vergleich zur anderen mehr Zeit in Anspruch nehmen.

Wenn wir also annehmen, dass die einzelnen Abrufzeiten für jede Komponente wie folgt verteilt sind:

  1. Das Laden von NotesSummary dauert 2 Sekunden.
  2. Das Laden von RecentActivity dauert 1 Sekunde.
  3. TagCloud braucht 3 Sekunden zum Laden.

Wenn wir auf „Aktualisieren“ klicken, sehen wir als Erstes 3 Skelettlader.

Nach 1 Sekunde wird die RecentActivity-Komponente angezeigt.
Nach 2 Sekunden folgt die NotesSummary und dann die TagCloud.

Anstatt den Benutzer also 3 Sekunden warten zu lassen, bevor er Inhalte sieht. Wir haben diese Zeit um 2 Sekunden verkürzt, indem wir zuerst die RecentActivity angezeigt haben.

Dieser inkrementelle Rendering-Ansatz führt zu einer besseren Benutzererfahrung und Leistung.

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

Der Code für die einzelnen Serverkomponenten ist unten hervorgehoben.

/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

Die Serverkomponente RecentActivity ruft grundsätzlich die letzten Aktivitäten mithilfe der Anwendungsfallfunktion getRecentActivity ab und stellt sie in einer ungeordneten Liste dar.

/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>
  )

}

Die serverseitige Komponente TagCloud ruft alle Tag-Namen ab, die im Inhalt der Notizen verwendet wurden, und rendert sie mit ihrer jeweiligen Anzahl.

/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 ...............*/}
</>
  )
}


Die Serverkomponente NotesSummary rendert die Zusammenfassungsinformationen, nachdem sie mit der Anwendungsfallfunktion getNoteSummary abgerufen wurde.

Profilseite (Funktionen für parallele Routen)

Jetzt gehen wir weiter zur Profilseite, wo wir eine interessante nextjs-Funktion namens Parallelrouten durchgehen.

Parallele Routen ermöglichen es uns, gleichzeitig oder bedingt eine oder mehrere Seiten innerhalb desselben Layouts zu rendern.

In unserem Beispiel unten rendern wir die Benutzerinformationsseite und die Benutzernotizenseite im selben Layout, nämlich /app/profile .

Sie können Parallele Routen erstellen, indem Sie benannte Slots verwenden. Ein benannter Slot wird genau als Unterseite deklariert, aber im Gegensatz zu normalen Seiten sollte das Symbol @ vor dem Ordnernamen stehen.

Zum Beispiel erstellen wir im Ordner /app/profile/ zwei benannte Slots:

  1. /app/profile/@info für die Benutzerinformationsseite.
  2. /app/profile/@notes für die Benutzernotizenseite.

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

Jetzt erstellen wir eine Layoutdatei /app/profile/layout.tsx, die das Layout unserer /profile-Seite definiert.

- 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

Wie Sie dem obigen Code entnehmen können, haben wir jetzt Zugriff auf die Parameter info und notes, die den Inhalt der Seiten @info und @notes enthalten.

Die @info-Seite wird also links gerendert und die @notes wird rechts gerendert.

Der Inhalt in page.tsx (referenziert durch children) wird am Ende der Seite gerendert.

@info-Seite

/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>
  )

}

Die UserInfoPage ist eine Serverkomponente, die die Benutzerinformationen mithilfe der Anwendungsfallfunktion getUserInfo abruft.

Das obige Fallback-Skelett wird an den Benutzerbrowser gesendet, wenn die Komponente Daten abruft und auf dem Server gerendert wird (serverseitiges 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 ...............*/}
</>
  )
}


@notes-Seite

Das Gleiche gilt für die serverseitige Komponente LastNotesPage. Es ruft Daten ab und rendert sie auf dem Server, während dem Benutzer eine Skelett-Benutzeroberfläche angezeigt wird

/app/profile/@notes/page.tsx

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/app/profile/@notes/loading.tsx

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

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

  return (
  <>

  <ServerComponent/>
  </>

  )
}


Fehlerseite

Lassen Sie uns nun eine ziemlich nette Funktion in Nextjs erkunden, die Seite error.tsx.

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

Wenn Sie Ihre Anwendung in der Produktion bereitstellen, möchten Sie sicherlich einen benutzerfreundlichen Fehler anzeigen, wenn ein nicht erfasster Fehler von einer Ihrer Seiten ausgegeben wird.

Hier kommt die Datei error.tsx ins Spiel.

Erstellen wir zunächst eine Beispielseite, die nach einigen Sekunden einen nicht erfassten Fehler auslöst.

/app/error-page/page.tsx

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


Wenn die Seite schläft oder auf die Ausführung der Ruhefunktion wartet. Dem Benutzer wird die untenstehende Ladeseite angezeigt.

/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>
}

Nach einigen Sekunden wird der Fehler ausgegeben und Ihre Seite wird gelöscht :(.

Um dies zu vermeiden, erstellen wir die Datei error.tsx, die eine Komponente exportiert, die als Fehlergrenze für die /app/error-page/page fungiert .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

Abschluss

In diesem Leitfaden haben wir die wichtigsten Funktionen von Next.js untersucht, indem wir eine praktische Notizenanwendung erstellt haben. Wir haben Folgendes abgedeckt:

  1. App Router mit Server- und Client-Komponenten
  2. Laden und Fehlerbehandlung
  3. Serveraktionen
  4. Datenabruf und Zwischenspeicherung
  5. Streaming und Spannung
  6. Parallele Routen
  7. Fehlergrenzen

Durch die Anwendung dieser Konzepte in einem realen Projekt haben wir praktische Erfahrungen mit den leistungsstarken Funktionen von Next.js gesammelt. Denken Sie daran: Der beste Weg, Ihr Verständnis zu festigen, ist durch Übung.

Nächste Schritte

  • Entdecken Sie den vollständigen Code: github.com/spithacode/next-js-features-notes-app
  • Erweitern Sie die Anwendung mit Ihren eigenen Funktionen
  • Bleiben Sie mit der offiziellen Next.js-Dokumentation auf dem Laufenden

Wenn Sie Fragen haben oder etwas weiter besprechen möchten, können Sie mich gerne hier kontaktieren.

Viel Spaß beim Codieren!

Das obige ist der detaillierte Inhalt vonNext.js Deep Dive: Erstellen einer Notizen-App mit erweiterten Funktionen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn