首頁  >  文章  >  web前端  >  Next.js 深入探討:建立具有高級功能的 Notes 應用程式

Next.js 深入探討:建立具有高級功能的 Notes 應用程式

DDD
DDD原創
2024-11-03 15:07:031033瀏覽

Next.js Deep Dive: Building a Notes App with Advanced Features## 簡介與目標

在這篇部落格文章中,我想介紹您在實際場景中需要的最重要的 Next.js 功能。

我創建這篇部落格文章作為我自己和感興趣的讀者的參考。而不必閱讀整個 nextjs 文件。我認為寫一篇精簡部落格文章包含所有接下來的重要實用功能會更容易,您可以定期訪問以刷新您的知識!

我們將在並行建立筆記應用程式的同時一起了解以下功能。

  • 應用路由器

    • 伺服器元件
    • 客戶端元件
    • 巢狀路由
    • 動態路線
  • 載入與錯誤處理

  • 伺服器操作

    • 建立和使用伺服器操作
    • 將伺服器操作與客戶端元件整合
  • 資料取得與快取

    • 使用unstable_cache進行伺服器端快取1.使用revalidateTag重新驗證緩存
    • 使用unstable_cache進行伺服器端快取
  • 串流媒體與懸念

    • 使用loading.tsx進行頁面級串流傳輸
    • 使用 Suspense 的組件級流
  • 平行路線

    • 建立並使用命名槽
    • 實作多個頁面元件同時渲染
  • 錯誤處理

    • 使用 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

請直接跳到最終程式碼,您可以在這個 Github 儲存庫 spithacode 中找到。

事不宜遲,讓我們開始吧!

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

關鍵概念

在深入開發我們的筆記應用程式之前,我想介紹一些關鍵的 nextjs 概念,在繼續之前了解這些概念非常重要。

應用程式路由器

App Router 是一個新目錄"/app",它支援許多舊版"/page" 目錄中不可能的功能,例如:

  1. 伺服器組件。
  2. 共享佈局:layout.tsx 檔案。
  3. 巢狀路由:您可以將資料夾嵌套在另一個資料夾中。頁面路徑url將遵循相同的資料夾嵌套。例如,假設[noteId]動態參數等於/app/notes/[noteId]/edit/page.tsx對應的url >“1” 「/notes/1/edit

  4. /loading.tsx 文件,該文件會匯出當頁面串流到使用者瀏覽器時呈現的元件。

  5. /error.tsx 文件,該文件匯出當頁面拋出一些未捕獲的錯誤時呈現的組件。

  6. 並行路由和我們在建立筆記應用程式時將要經歷的許多功能。

伺服器元件與客戶端元件

讓我們深入探討一個非常重要的主題,每個人在接觸 Nextjs

/app Router 之前都應該掌握這個主題。

伺服器元件

伺服器元件基本上是在伺服器上呈現的元件。

任何前面沒有

「use client」指令的元件預設都是伺服器元件,包括頁面和佈局。

伺服器元件可以與任何nodejs API或任何要在伺服器上使用的元件互動。

客戶端元件不同,可以在伺服器元件之前加上async關鍵字。因此,您可以調用任何非同步函數並在渲染元件之前等待它。

- 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

您可能會想為什麼要在伺服器上預先渲染元件?

答案可以用幾句話來概括

SEO、效能和使用者體驗。

當使用者造訪頁面時,瀏覽器會下載網站資源,包括 html、css 和 javascript。

由於其大小,JavaScript 套件(包括您的框架程式碼)比其他資源需要更多的時間來載入。

  • 因此使用者必須等待才能在螢幕上看到某些內容。

  • 同樣的事情也適用於負責為您的網站建立索引的

    爬蟲

  • 許多其他

    SEO 指標,例如 LCPTTFB跳出率...都會受到影響。

客戶端組件

客戶端元件 只是一個傳送到使用者瀏覽器的元件。

客戶端元件不只是裸露的 html 和 css 元件。它們需要交互性才能工作,因此實際上不可能在伺服器上渲染它們。

互動性由像react(useState,useEffect)這樣的javascript框架或僅瀏覽器或DOM API來保證。

客戶端元件宣告之前應有「use client」指令。這告訴 Nextjs 忽略它的互動部分(useState、useEffect...)並將其直接發送到使用者的瀏覽器。

/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

不同的可組合性排列。

我知道,Nextjs 中最令人沮喪的事情是,如果您錯過了伺服器元件客戶端元件 之間的巢狀規則,您可能會遇到那些奇怪的錯誤。

因此,在下一節中,我們將透過展示 伺服器元件客戶端元件 之間可能的不同巢狀排列來澄清這一點。

我們將跳過這兩種排列,因為它們顯然是允許的:客戶端元件另一個客戶端元件和另一個伺服器元件內的伺服器元件。

將伺服器元件呈現為客戶端元件的子元件

您可以匯入客戶端元件並在伺服器元件內正常渲染它們。這種排列很明顯,因為 pageslayouts 預設情況下是伺服器元件。

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

}

將伺服器元件呈現為客戶端元件的子元件

想像一下將客戶端元件傳送到使用者的瀏覽器,然後等待位於其中的伺服器元件來渲染和取得資料。這是不可能的,因為伺服器元件已經發送到客戶端,那麼如何在伺服器上渲染它?

這就是為什麼 Nextjs 不支援這種類型的排列。

因此請始終記住避免將 伺服器元件 匯入到 客戶端元件 中以將它們渲染為子元件。

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


始終嘗試透過在 jsx 樹中下推客戶端元件來減少發送到使用者瀏覽器的 JavaScript。

在客戶端元件內渲染伺服器元件的解決方法

不可能直接導入和渲染伺服器元件作為客戶端元件的子元件,但是有一個解決方法可以利用反應可組合性.

技巧是將 伺服器元件 作為 客戶端元件 的子級傳遞到更高層級的伺服器元件 (ParentServerComponent)。

我們稱為爸爸把戲:D.

此技巧可確保傳遞的 伺服器元件 在將 客戶端元件 傳送到使用者的瀏覽器之前在伺服器上呈現。

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



我們將在筆記應用程式的 /app/page.tsx 主頁看到一個具體範例。

我們將在其中渲染作為客戶端元件內的子元件傳遞的伺服器元件。客戶端元件可以根據布林狀態變數值有條件地顯示或隱藏伺服器元件渲染的內容。

伺服器動作

伺服器操作是一個有趣的nextjs功能,它允許遠端呼叫遠端安全地客戶端元件在伺服器上聲明的函數.

要聲明伺服器操作,您只需將「use server」指令新增至函式主體中,如下所示。

- 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

「use server」指令告訴Nextjs該函數包含僅在伺服器上執行的伺服器端程式碼。

在底層,Nextjs 傳送 操作 ID 並為此操作建立一個保留端點。

因此,當您在客戶端元件 中呼叫此操作時,Nextjs 將對由Action Id 標識的操作唯一端點執行POST 請求,同時傳遞您在呼叫請求正文中的操作時傳遞的序列化參數。

讓我們透過這個簡化的範例來更好地闡明這一點。

我們之前看到,您需要在函數體指令中使用 「use server」 來宣告伺服器操作。但是如果您需要一次聲明一堆伺服器操作怎麼辦?

嗯,您可以在檔案頭或檔案開頭使用該指令,如下面的程式碼所示。

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

}

請注意,伺服器操作應始終標記為非同步

  • 所以在上面的程式碼中,我們宣告了一個名為 createLogAction.

  • 的伺服器操作
  • 此操作負責將日誌項目保存在伺服器上 /logs 目錄下的特定檔案中。

  • 檔案依 名稱 操作參數命名。

  • 操作附加一個日誌條目,其中包含建立日期和訊息操作參數。

現在,讓我們在 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 ...............*/}
</>
  )
}


按鈕元件聲明了一個名為 isSubmitting 的本地狀態變量,用於追蹤操作是否正在執行。執行操作時,按鈕文字從 「登入按鈕」 變更為 「正在載入...」

當我們點擊日誌按鈕元件時,將呼叫伺服器操作

業務邏輯設定

建立我們的 Note 模型

首先,讓我們從建立註解驗證架構和類型開始。

由於模型應該處理資料驗證,我們將使用一個流行的函式庫來實現此目的,稱為 zod。

zod 的酷之處在於其描述性且易於理解的 API,這使得定義模型和產生相應的 TypeScript 成為一項無縫任務。

我們不會在筆記中使用奇特的複雜模型。每個筆記都有一個唯一的 ID、標題、內容和建立日期欄位。

- 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

我們也聲明了一些有用的附加模式,例如 InsertNoteSchema 和WhereNoteSchema,當我們建立稍後操作模型的可重複使用函數時,這將使我們的生活變得更輕鬆。

建立一個簡單的記憶體資料庫

我們將在記憶體中儲存和操作我們的筆記。

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

}

我們將註解陣列儲存在全域 this 物件中,以避免每次將註解常數匯入到檔案中時遺失陣列的狀態(頁面重新載入...)。

創建我們的應用程式用例

建立註釋用例

createNote 用例將允許我們將註解插入到註解陣列中。將 notes.unshift 方法視為 notes.push 方法的逆方法,因為它將元素推送到數組的開頭而不是末尾。

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


更新說明用例

我們將使用 updateNote 更新註解數組中給定其 id 的特定註解。它首先查找元素的索引,如果沒有找到則拋出錯誤,並根據找到的索引返回相應的註釋。

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



刪除註釋用例

deleteNote 用例函數將用於刪除給定筆記 ID 的給定筆記。
該方法的工作原理類似,首先它根據給定的 id 查找註釋的索引,如果未找到則拋出錯誤,然後返回由找到的 id 索引的相應註釋。

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

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

  return (
  <>

  <ServerComponent/>
  </>

  )
}


取得註釋用例

getNote 函數是不言自明的,它只會根據給定的 id 找出一條註解。

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


取得筆記用例

由於我們不想將整個筆記資料庫推送到客戶端,因此我們只會取得可用筆記總數的一部分。因此我們需要實作伺服器端分頁。

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

因此 getNotes 函數基本上允許我們透過傳遞 page 參數從伺服器取得特定頁面。
limit 參數用於決定給定頁面上存在的項目數量。

例如:
如果 notes 陣列包含 100 個元素,且 limit 參數等於 10。

透過向我們的伺服器要求第 1 頁,只會傳回前 10 項。

search 參數將用於實現伺服器端搜尋。它將告訴伺服器僅傳回將 search 字串作為標題或內容屬性中的子字串的註解。

取得筆記摘要用例

- 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

取得最近的活動用例

此用例將用於獲取有關用戶最近活動的一些虛假資料。

我們將在 /dashboard 頁面中使用此功能。

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

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

  const pasta = await getPasta()

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

      ))

    }
  </ul>
  )

}

取得最近的標籤用例

此用例函數將負責獲取有關我們筆記中使用的不同標籤的統計資料(#something)。

我們將在 /dashboard 頁面中使用此功能。

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

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

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

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


取得用戶資訊用例

我們將使用這個用例函數來傳回一些有關某些使用者資訊的虛假數據,例如姓名、電子郵件......

我們將在 /dashboard 頁面中使用此功能。

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



取得隨機筆記用例

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

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

  return (
  <>

  <ServerComponent/>
  </>

  )
}


應用程式路由器伺服器操作和快取

主頁(伺服器元件內部客戶端元件解決方法示範)

在此主頁中,我們將示範先前的技巧或解決方法,用於在客戶端元件內渲染伺服器元件PaPa技巧:D) .

/app/page.tsx

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


在上面的程式碼中,我們聲明了一個名為Home父伺服器元件,它負責在我們的應用程式中渲染"/" 頁面。

我們正在導入一個名為 RandomNoteServer Component 和一個名為 NoteOfTheDayClientonent

我們將 RandomNote 伺服器元件作為子元件傳遞給 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>
}

RandomNote 伺服器元件的工作原理如下:

  • 它使用 getRandomNote 用例函數來取得隨機註解。

  • 它呈現由標題和完整註釋的部分或子字串內容組成的註釋詳細資訊。

/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

另一側的 NoteOfTheDay 客戶端元件的工作原理如下:

  • 它將Children 屬性作為輸入(在我們的例子中,這將是我們的RandomNote 伺服器元件),然後根據isVisible 布林狀態變數值有條件地渲染它。
  • 元件也會渲染一個按鈕,並附加一個 onClick 事件偵聽器,以切換可見性狀態值。

註釋頁

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

}

我們會先建立 /app/notes/page.tsx 頁面,它是伺服器元件,負責:

  1. 取得頁面搜尋參數,即附加在 URL 末尾 ? 標記後的字串:http://localhost:3000/notes?page=1&search=Something

  2. 將搜尋參數傳遞到名為 fetchNotes.

  3. 的本地宣告函數中
  4. fetchNotes 函數使用我們先前宣告的用例函數 getNotes 來取得目前筆記頁面。

  5. 您可以注意到,我們正在使用從"next/cache" 導入的名為unstable_cache 的實用函數來包裝getNotes 函數。不穩定的快取函數用於快取 getNotes 函數的回應。

如果我們確定資料庫中沒有新增任何註解。每次重新加載頁面時都點擊它是沒有意義的。因此unstable_cache 函數使用"notes" 標籤標記getNotes 函數結果,我們稍後可以使用該標籤使"notes" 快取新增或刪除註釋。

  1. fetchNotes 函數傳回兩個值:音符和總計。

  2. 結果資料(筆記和總計)被傳遞到一個名為

    NotesList客戶端元件,它負責渲染我們的筆記。

當使用者點擊刷新時。當我們的筆記資料被取得時,使用者會看到一個空白頁面。

為了解決這個問題,我們將使用一個很棒的 Nextjs 功能,稱為。
伺服器端頁面流

我們可以透過在

/app/notes/page.tsx 檔案旁邊建立 loading.tsx 檔案來做到這一點。

/app/notes/loading.tsx


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

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

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

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


當頁面從伺服器串流時,使用者將看到一個框架載入頁面,這讓使用者了解即將到來的內容類型。

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

那不是很酷嗎:)。只要建立一個loading.tsx 文件,瞧,你就完成了。您的用戶體驗正在提升到一個新的水平。

/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

註解清單 客戶端元件 從其父 伺服器元件(即 NotesPage

接收註解與分頁相關資料)

然後元件處理渲染目前筆記頁面。每個單獨的筆記卡均使用 NoteView 元件呈現。

它還使用Next.js Link 組件提供上一頁和下一頁的鏈接,該組件對於預獲取下一頁和上一頁數據至關重要,以便我們擁有一個無縫且快速的客戶端側邊導航。

為了處理伺服器端搜尋,我們使用一個名為useNotesSearch的自訂鉤子,它基本上處理當用戶在搜尋中鍵入特定查詢時觸發筆記重新取得輸入.

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

}

NoteView 元件很簡單,它只負責渲染每個單獨的筆記卡及其相應的:標題、部分內容以及用於查看筆記詳細資訊或編輯它的操作連結。

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


useNotesSearch 自訂掛鉤的工作原理如下:

  1. 它使用 useState 鉤子將 initialSearch 屬性儲存在本地狀態中。

  2. 我們使用 useEffect React 鉤子在 currentPagedebouncedSearchValue 變數值變更時觸發頁面導覽。

  3. 新的頁面 URL 是在考慮當前頁面和搜尋值的情況下建立的。

  4. 當使用者在搜尋輸入中鍵入內容時,每次字元變更時都會呼叫 setSearch 函數。這會導致短時間內導航過多。

  5. 為了避免我們只在使用者停止輸入其他術語時觸發導航,我們將在特定的時間內(在我們的例子中為 300 毫秒)對搜尋值進行去抖動。

建立註釋

接下來,讓我們瀏覽一下 /app/notes/create/page.tsx,它是 CreateNoteForm 客戶端元件的伺服器元件包裝器。

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

/app/notes/create/page.tsx

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/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

CreateNoteForm 客戶端元件表單負責從使用者檢索數據,然後將其儲存在本地狀態變數(標題、內容)中。

點選提交按鈕提交表單後,createNoteAction將與titlecontent本地狀態參數一起提交.

content本地狀態參數一起提交.

isSubmitting狀態布林變數用於追蹤操作提交狀態。 如果 createNoteAction

成功提交且沒有任何錯誤,我們會將使用者重新導向到
/notes

頁面。

/app/notes/create/actions/create-note.action.tsx
export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

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

  const pasta = await getPasta()

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

      ))

    }
  </ul>
  )

}

createNoteAction

操作程式碼很簡單,包含檔案前面帶有

「use server」

指令,指示 Next.js 操作可在客戶端元件中呼叫。

關於伺服器操作,我們應該強調的一點是,只有操作介面被傳送到客戶端,而不是操作本身內部的程式碼。

換句話說,操作中的程式碼將駐留在伺服器上,因此我們不應該信任從客戶端到我們伺服器的任何輸入。

這就是為什麼我們在這裡使用 zod 來使用我們之前建立的模式來驗證

rawNote 操作參數。

驗證輸入後,我們將使用經過驗證的資料呼叫

createNote 用例。 如果筆記建立成功,則會呼叫revalidateTag 函數來使標記為"notes" 的快取條目失效(記住unstable_cache

函數用於

/notes

頁)。

備註詳情頁

Next.js Deep Dive: Building a Notes App with Advanced Features筆記詳細資料頁面根據其唯一 ID 呈現特定筆記的標題和完整內容。除此之外,它還顯示一些用於編輯或刪除註釋的操作按鈕。


/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. 首先,我們從頁面道具中檢索頁面參數。在 Next.js 13 中,我們必須等待 params 頁面參數,因為它是一個承諾。

    完成此操作後,我們將 params.noteId

    傳遞給
  2. 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. fetchNote 函數使用unstable_cache 包裝我們的getNote用例,同時用「note-details」標記傳回的結果,同時用「note-details」標記傳回的結果> 和note-details/${id} 標籤。

  2. 「note-details」標籤可用於一次使所有筆記詳細資料快取項目失效。

  3. 另一方面,note-details/${id} 標籤僅與其唯一 id 定義的特定註解相關聯。因此我們可以使用它來使特定筆記的快取條目無效,而不是使整個筆記集無效。

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

}

提醒

loading.tsx 是一個特殊的 Next.js 頁面,當筆記詳細資料頁面在伺服器上取得其資料時呈現。

或者換句話說,當fetchNote函數正在執行時,將向使用者顯示骨架頁面而不是空白螢幕。

這個 nextjs 功能稱為 頁面流。它允許發送動態頁面的整個靜態父佈局,同時逐漸串流其內容。

這可以避免在伺服器上取得頁面的動態內容時阻塞使用者介面,從而提高效能和使用者體驗。

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


現在讓我們深入了解 DeleteNoteButton 客戶端元件。

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

此元件負責渲染刪除按鈕並執行 deleteNoteAction,然後在操作成功執行時將使用者重新導向至 /notes 頁面。

為了追蹤操作執行狀態,我們使用本地狀態變數isDeleting.

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

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



deleteNoteAction 程式碼的工作原理如下:

  1. 它使用 zod 來解析和驗證操作輸入。
  2. 確保我們的輸入安全後,我們將其傳遞給 deleteNote 用例函數。
  3. 當操作成功執行時,我們使用 revalidateTag 使 "notes"note-details/${where.id} 快取失效條目。

編輯註釋頁

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

/app/notes/[noteId]/edit/page.tsx 頁面是一個伺服器元件,它從 params Promise 取得 noteId 參數。

然後它使用 fetchNote 函數取得註解。

成功取得後。它將註解傳遞給 EditNoteForm 客戶端元件。

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

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

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

  const pasta = await getPasta()

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

      ))

    }
  </ul>
  )

}

EditNoteForm 客戶端元件接收註解並呈現一個表單,允許使用者更新註解的詳細資訊。

titlecontent 局部狀態變數用於儲存其對應的輸入或文字區域值。

透過更新註解按鈕提交表單時。 updateNoteAction 被調用,並以 titlecontent 值作為參數。

isSubmitting 狀態變數用於追蹤操作提交狀態,允許在操作執行時顯示載入指示器。

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


updateNoteAction 操作的工作原理如下:

  1. 操作輸入使用對應的 zod 架構(WhereNoteSchemaInsertNoteSchema)進行驗證。
  2. 之後,使用解析和驗證的資料呼叫 updateNote 用例函數。
  3. 成功更新筆記後,我們重新驗證 "notes"note-details/${where.id} 標籤。

儀表板頁面(元件級串流功能)

/app/dashboard/page.tsx

import { ClientComponent } from '@/components'

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

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/app/dashboard/page.tsx 頁面被分解為更小的伺服器端元件:NotesSummaryRecentActivity 和TagCloudRecentActivity 和TagCloud

RecentActivity.

每個伺服器元件獨立取得自己的資料。

每個伺服器元件都包裝在 React Suspense

邊界。

懸念邊界的作用是當子伺服器元件取得自己的資料時顯示後備元件(在我們的例子中是一個骨架

)。

或者換句話說,Suspense

邊界允許我們延遲或延遲其子級的渲染,直到滿足某些條件(正在載入子級內的資料)。

因此使用者將能夠將頁面視為一堆骨架的組合。伺服器正在傳輸每個單獨組件的回應。

這種方法的一個關鍵優點是,如果一個或多個伺服器元件比另一個元件花費更多時間,可以避免阻塞 UI。

因此,如果我們假設每個組件的單獨獲取時間分佈如下:

  1. NotesSummary載入需要 2 秒。
  2. 最近活動需要 1 秒加載。
  3. TagCloud載入需要 3 秒。

當我們點擊刷新時,我們首先看到的是 3 個骨架載入器。

1 秒後,RecentActivity 元件將會顯示。
2 秒後,NotesSummary 將緊隨其後,然後是 TagCloud

所以不要讓使用者等待 3 秒鐘才能看到任何內容。我們透過先顯示 RecentActivity 將時間縮短了 2 秒。

這種增量渲染方法可以帶來更好的使用者體驗和效能。

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

各個伺服器元件的程式碼在下面突出顯示。

/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

RecentActivity 伺服器元件基本上使用 getRecentActivity 使用案例函數來取得最後的活動,並將它們呈現在無序列表中。

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

}

TagCloud 伺服器端元件取得然後渲染筆記內容中使用的所有標籤名稱及其各自的計數。

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


NotesSummary 伺服器元件在使用 getNoteSummary 用例函數取得摘要資訊後呈現摘要資訊。

個人資料頁面(並行路由功能)

現在讓我們進入個人資料頁面,在這裡我們將介紹一個有趣的 nextjs 功能,稱為 並行路由

並行路線允許我們同時有條件渲染一個或多個頁面在同一版面內

在下面的範例中,我們將在 /app/profile 相同的佈局中渲染 使用者資訊頁面使用者註解頁面 .

您可以使用命名槽建立並行路由。命名槽完全被聲明為子頁面,但與普通頁面不同,@ 符號應位於資料夾名稱之前。

例如,在 /app/profile/ 資料夾中,我們將建立兩個命名槽:

  1. /app/profile/@info 用於使用者資訊頁面。
  2. /app/profile/@notes 用於使用者註解頁面。

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

現在讓我們建立一個佈局文件 /app/profile/layout.tsx 文件,它將定義 /profile 頁面的佈局。

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

如您從上面的程式碼中看到的,我們現在可以存取 infonotes 參數,其中包含 @info 和 @notes 頁面內的內容。

因此 @info 頁面將呈現在左側,@notes 將呈現在右側。

page.tsx 中的內容(由 children 引用)將呈現在頁面底部。

@資訊頁面

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

}

UserInfoPage 是一個伺服器元件,它將使用 getUserInfo 用例函數來取得使用者資訊。

當元件取得資料並在伺服器上渲染時(伺服器端串流),上述後備框架將會傳送到使用者瀏覽器。

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

同樣的事情也適用於 LastNotesPage 伺服器端元件。它將獲取資料並在伺服器上渲染,同時向使用者顯示骨架 UI

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

  )
}


錯誤頁面

現在讓我們來探索 Nextjs 中的一個非常好的功能 error.tsx 頁面。

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

當您將應用程式部署到生產環境時,您肯定會希望在某個頁面拋出未捕獲的錯誤時顯示用戶友好的錯誤。

這就是 error.tsx 文件出現的地方。

讓我們先建立一個範例頁面,該頁面會在幾秒鐘後拋出未捕獲的錯誤。

/app/error-page/page.tsx

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


當頁面睡眠或等待睡眠函數執行。將向使用者顯示以下載入頁面。

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

幾秒鐘後,將拋出錯誤並刪除您的頁面:(。

為了避免這種情況,我們將建立error.tsx 文件,該文件匯出一個元件,該元件將充當/app/error-page/page 的錯誤邊界 .tsx.

/app/error-page/error.tsx

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

結論

在本指南中,我們透過建立實用的筆記應用程式探索了 Next.js 的關鍵功能。我們涵蓋了:

  1. 有伺服器和客戶端元件的應用程式路由器
  2. 載入與錯誤處理
  3. 伺服器操作
  4. 資料取得與快取
  5. 串流媒體與懸念
  6. 平行路線
  7. 誤差邊界

透過將這些概念應用到實際專案中,我們獲得了 Next.js 強大功能的實務經驗。請記住,鞏固理解的最好方法是透過實踐。

下一步

  • 探索完整程式碼:github.com/spithacode/next-js-features-notes-app
  • 用自己的功能擴充應用程式
  • 隨時了解官方 Next.js 文件的更新

如果您有任何疑問或想進一步討論,請隨時與我聯繫。

編碼愉快!

以上是Next.js 深入探討:建立具有高級功能的 Notes 應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn