Heim > Artikel > Web-Frontend > Next.js Deep Dive: Erstellen einer Notizen-App mit erweiterten Funktionen
## 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
Laden und Fehlerbehandlung
Serveraktionen
Datenabruf und Zwischenspeicherung
Streaming und Spannung
Parallele Routen
Fehlerbehandlung
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!
Bevor ich mich mit der Entwicklung unserer Notizenanwendung befasse, möchte ich einige wichtige nextjs-Konzepte vorstellen, die Sie kennen sollten, bevor Sie fortfahren.
Der App Router ist ein neues Verzeichnis "/app", das viele Dinge unterstützt, die im alten Verzeichnis "/page" nicht möglich waren, wie z :
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.
/app Router überhaupt berührt.
ServerkomponentenServerkomponente 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.tsSie 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.
Crawler, die für die Indexierung Ihrer Website verantwortlich sind.
SEO-Metriken wie LCP, TTFB, Absprungrate,... werden davon betroffen sein.
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.
DieInteraktivitä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
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.
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> ) }
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.
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, dieServerkomponente als untergeordnetes Element der Clientkomponente an einer Serverkomponente höherer Ebene ( ParentServerComponent) zu übergeben.
Nennen wir es denPapa-Trick :D.
Dieser Trick stellt sicher, dass die übergebeneServerkomponente 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.
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.
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.
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...).
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 ...............*/} </> ) }
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/> </> ) }
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/> </> ) }
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> </> ) }
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.
- 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
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> ) }
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 ...............*/} </> ) }
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/> </> ) }
"use client" import { ServerComponent } from '@/components' // Not allowed :( export const ClientComponent = ()=>{ return ( <> <ServerComponent/> </> ) }
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:
/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:
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
Übergabe der Suchparameter an eine lokal deklarierte Funktion namens fetchNotes.
Die Funktion fetchNotes verwendet unsere zuvor deklarierte Anwendungsfallfunktion getNotes, um die aktuelle Notizenseite abzurufen.
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.
fetchNotes gibt zwei Werte zurück: die Notizen und die Gesamtsumme.
Client-seitige Komponente namens NotesList übergeben, die für die Darstellung unserer Notizen verantwortlich ist.
Um dieses Problem zu lösen, verwenden wir eine tolle Nextjs-Funktion namens.
Serverseitiges Seiten-Streaming.
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.
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:
Es speichert die Requisite initialSearch in einem lokalen Zustand mithilfe des Hooks useState.
Wir verwenden den React-Hook useEffect, um eine Seitennavigation auszulösen, wenn sich die Variablenwerte currentPage oder debouncedSearchValue ändern.
Die neue Seiten-URL wird unter Berücksichtigung der aktuellen Seite und der Suchwerte erstellt.
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.
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).
Als nächstes gehen wir die /app/notes/create/page.tsx durch, die ein Serverkomponenten-Wrapper um die CreateNoteForm-Clientkomponente ist.
/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).
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.
/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 ...............*/} </> ) }
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.
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
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.
Das Tag "note-details" kann verwendet werden, um alle Notizdetails-Cache-Einträge auf einmal ungültig zu machen.
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.
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:
/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:
/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:
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.
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.
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:
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.
/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 ...............*/} </> ) }
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/> </> ) }
Lassen Sie uns nun eine ziemlich nette Funktion in Nextjs erkunden, die Seite error.tsx.
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
In diesem Leitfaden haben wir die wichtigsten Funktionen von Next.js untersucht, indem wir eine praktische Notizenanwendung erstellt haben. Wir haben Folgendes abgedeckt:
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.
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!