Next.js 實戰筆記 2.0:深入 App Router 高階特性與布局解構

Next.js 實戰筆記 2.0:深入 App Router 高階特性與布局解構

上一篇筆記:

  • Next.js 實戰筆記 1.0:架構重構與 App Router 核心機制詳解

上篇筆記主要回顧了一些 Next12 到 Next15 的一些變化,這里繼續學習/復習一些已有或者是新的變化

turbo 的補充

在實際運行的過程當中,我發現使用 yarn dev --turbo 運行,編譯并不穩定——不確定是因為我的 Mac 還是 intel 的原因,畢竟現在很多的優化都是針對 M 芯片做的,總之目前還是 fallback 到了默認的開發模式……

其他保留頁面

除了 page.jslayout.js 之外,NextJS 還有其他兩個保留頁面

報錯頁面

也就是 error.js,大體的實現如下:

"use client";
import React from "react";const MealsErrorPage = () => {return (<main className="error"><h1>An Error Occurred!</h1><p>Fail to fetch meal data. Please try again later.</p></main>);
};export default MealsErrorPage;

需要注意的是, error.js 必須要使用 use client,因為這個頁面即會處理 server end 的異常,也會處理 client end 的異常

它的作用與 layout 類似,在當前/兄弟姐妹/子頁面出現異常后,會渲染當前頁面

not found

大體實現如下:

import React from "react";const NotFoundPage = () => {return (<main className="not-found"><h1>Not Found</h1><p>Could not find the page you are looking for.</p></main>);
};export default NotFoundPage;

error.js 類似,不過在組件內調用 notFound(); 也可以重定向到當前頁面

表單

其實這部分不完全是 NextJS 的內容,更多的是 React 19 提出的新功能。這里會基于 NextJS 中的實現進行討論,React 的話,等到 NextJS 的內容過完了后,重新過一遍 React18 和 19 的新特性

提交表單

之前在使用 React 的表單時,提交事件其實不由 action 觸發,而是通過 onClick + preventDefault() 可以繞過 action 進行實現。不過目前 NextJS 目前則可以直接通過 action 在 server end 完成表單的提交,并且將表單中有的數據包成 formData 作為參數

下面是一個簡單的實現:

export default function ShareMealPage() {const shareMeal = async (formData) => {// use server must be an async function"use server";const meal = {creator: formData.get("name"),creator_email: formData.get("email"),title: formData.get("title"),summary: formData.get("summary"),instructions: formData.get("instructions"),image: formData.get("image"),};console.log(meal);};return (<><header className={classes.header}><h1>Share your <span className={classes.highlight}>favorite meal</span></h1><p>Or any other meal you feel needs sharing!</p></header><main className={classes.main}><form className={classes.form} action={shareMeal}></form></main></>);
}

服務端輸出的結果:

這里需要注意的是,如果組件本身使用了 use client,那么在方法內使用 use server 就會報錯……

useFormStatus

這里簡單的提一下使用方法,就是一個返回的 pending 可以更靈活的運用

const { pending, data, method, action } = useFormStatus();

具體的使用案例如下:

"use client";import React from "react";
import { useFormStatus } from "react-dom";const MealsFormSubmit = () => {const { pending } = useFormStatus();return (<button disabled={pending}>{pending ? "Submitting" : "Share Meal"}</button>);
};export default MealsFormSubmit;

我這里是單獨拆了一個組件出來使用,這個方法和官方提供的使用方法類似:

import { useFormStatus } from "react-dom";
import action from "./actions";function Submit() {const status = useFormStatus();return <button disabled={status.pending}>Submit</button>;
}export default function App() {return (<form action={action}><Submit /></form>);
}

具體的操作,React 在內部已經實現了,只要通過 action 進行觸發,就可以順利地監聽到表單的狀態變化

useFormState

目前 React 官方是把 useFormState 重命名成了 useActionState,并且用法是一樣的——除了后者是從 react 中導入,前者是 react-dom 中導入:

In earlier React Canary versions, this API was part of React DOM and called useFormState.

但是我看了下,不知道為啥用 useActionState 會報錯,用 useFormState 暫時沒問題。介于我用的這個版本,useFormState 還沒有被移除,因此暫時就使用了 useFormState

hook 的 signature 如下:

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

同理,因為是 hook,所以也需要使用 use client

具體使用方法如下:

"use client";import ImagePicker from "@/components/meals/image-picker";
import classes from "./page.module.css";
import { shareMeal } from "@/lib/action";
import MealsFormSubmit from "@/components/meals/meals-form-submit";
import { useFormState } from "react-dom";export default function ShareMealPage() {const [state, formAction] = useFormState(shareMeal, { message: null });return (<><header className={classes.header}><h1>Share your <span className={classes.highlight}>favorite meal</span></h1><p>Or any other meal you feel needs sharing!</p></header><main className={classes.main}><form className={classes.form} action={formAction}><p className={classes.actions}>{state.message && <p>{state.message}</p>}</p></form></main></>);
}

shareMeal 的實現如下:

export const shareMeal = async (prevState, formData) => {const meal = {creator: formData.get("name"),creator_email: formData.get("email"),title: formData.get("title"),summary: formData.get("summary"),instructions: formData.get("instructions"),image: formData.get("image"),};if (isInvalidText(meal.title) ||isInvalidText(meal.summary) ||isInvalidText(meal.instructions) ||isValidEmail(meal.creator_email) ||isValidEmail(meal.creator) ||!meal.creator_email.includes("@") ||!meal.image ||meal.image.size === 0) {return {message: "Invalid input",};}await saveMeal(meal);redirect("/meals/");
};

這部分其實沒什么特別好深入挖掘的,使用方法和官方文檔基本一致,屬于跟著官方文檔實現就好了,大體需要注意的地方有:

  • form 的 action 需要使用 useFormState 返回的第二個值,這樣方便 React 進行監聽
  • 原本的 action fn 第一個參數需要接受 initialState 作為第一個參數

💡:我個人覺得,將 useFormStateuseFormStatus 封裝成一個通用的 custom hook,保證全局的 initialState 一致,這樣處理起來可能會更加的高效,也可以更好地減少 boilerplate 代碼

緩存

這部分主要是使用 revalidatePath() 這個方法,在進行重定向的時候,去清除 NextJS 中存在的緩存

說實話,這部分的內容可能真的是要多做一點 deploy 之后,才有更多的感覺。目前我有一個小項目是通過 NextJS+github actions 部署到 GH Pages 上的,我只能說似乎是因為 use client 的關系,頁面還是會零零碎碎的去 fetch 一些小的 JS 文件。只不過因為頁面整體的內容比較少,加載速度還是比較快——大概在 100-200ms 之間,因此目前我還沒有花太多的時間和心力去研究 deploy 這部分的內容

dynamic metadata

metadata 的內容在 1.0 中已經提過了,這里講的是動態的 metadata 的實現方式,主要是通過這個 generateMetadata 的方法自動生成的。 generateMetadata 也是一個保留詞,具體使用方法如下:

export const generateMetadata = async ({ params }) => {const meal = await getMeal(params.mealSlug);return {title: meal.title,description: meal.summary,};
};

路由

這里再多提一些關于路由的內容,更多更完整的內容,還是可以到官方文檔: **Project structure and organization** 中去去查找,并且自己測試試驗,再根據項目需求判斷是否需要

parallel routes

個人感覺,parallel routes 是一個更方便管理子組件的一種實現。官方文檔中說了,parallel routes 的實現必須要依賴于 layout.js ,而且 parallel routes,也就是用 @folder 這種語法,會生成獨立的 slot,但是不會生成獨立的 URL

如下面這個案例:

@archive@latest 會作為兩個獨立的 slots,可以在 layout.js 中獲取,但是它的路徑還是在 localhost:3000/archive 下,單獨訪問 localhost:3000/archive/@archive 或是 localhost:3000/archive/@latest 會報錯,因為 NextJS 內部并沒有實現對應的路徑

具體的排列方式如下:

import React from "react";const ArchiveLayout = ({ archive, latest }) => {return (<div><h1>News Archive</h1><section id="archive-filter">{archive}</section><section id="archive-latest">{latest}</section></div>);
};export default ArchiveLayout;

這種情況下, archivelatest 的內容會被并排渲染:

parallel routes + 動態路由

現在總體來說,需求還是比較明確的:

  • archive 顯示按照年月分類的文檔
  • latest 顯示最近的幾個文檔

按照 NextJS 的結構,那么文檔目錄就應該是現在這個樣子的:

不過這就造成了一個問題:

這是因為,parallel routes 中的路徑存在不匹配的情況—— @archive 下有 [year],但是 @latest 下面沒有,NextJS 沒有辦法完美匹配路徑,因此就拋出了異常

這種情況下解決方式有兩種:

  1. @latest 下也創立對應的 [year] 結構

    缺點就是語意不明確,而且會增加很多無意義的結構

    在當前的業務情況下,@latest 默認只會顯示最近的幾條數據,并不需要根據 年/月 進行搜索

  2. 使用 default.js

    default.js 是 parallel route 的 fallback 頁面,具體實現如下:

    💡 這里的 default.js 中的內容和 page.js 完全一致,因此后期實現中將 page.js 刪除了

最終渲染效果如下:

剛開始看到這個 @ 的用法還是不太理解,后面回顧了一下過去做的幾個項目,發現這個 slots 還是可以比較好的解決過去項目中,我碰到的幾個痛點:

  • 超大表單
    這個在填寫付款方法、地址的時候經常碰上,不過我們那時候的業務場景更復雜一些,總體上來說大概會有 6-7 個 steps,每個 steps 的路徑一致,但是表單不一樣
  • 同一個路徑中根據不同條件渲染不同內容

catch all route

其實 NextJS 還是提供了其他的不同實現方法,這個業務場景下,因為只有 年/月 的搜查,其實創建對應的文件夾結構也不是不行,而且對于 NotFound 的支持會更好一些。不過案例中選擇用了 catch all route 這個也比較常見實現進行學習

組件部分的實現比較簡單:

import NewsList from "@/app/_components/news-list";
import {getAvailableNewsMonths,getAvailableNewsYears,getNewsForYear,getNewsForYearAndMonth,
} from "@/app/_lib/news";
import Link from "next/link";
import React from "react";const FilteredNewsPage = ({ params }) => {const filter = params.filter;const selectedYear = filter?.[0];const selectedMonth = filter?.[1];let news;let links = getAvailableNewsYears();if (selectedYear && !selectedMonth) {news = getNewsForYear(selectedYear);links = getAvailableNewsMonths(selectedYear);} else if (selectedYear && selectedMonth) {news = getNewsForYearAndMonth(selectedYear, selectedMonth);links = [];}let newsContent = <p>No news found for the selected period.</p>;if (news?.length) {newsContent = <NewsList news={news} />;}return (<><header id="archive-header"><nav><ul>{links.map((link) => {const href = selectedYear? `/archive/${selectedYear}/${link}`: `/archive/${link}`;return (<li key={link}><Link href={href}>{link}</Link></li>);})}</ul></nav></header>{newsContent}</>);
};export default FilteredNewsPage;

這里需要注意的是 params 的返回值,從字符串變成了數組。這是 catch all 的特性,也就是攔截所有的 params

目錄結構如下:

需要注意的是這種情況下, @archive 下的 page.js 就會導致沖突,因為 [[...filter]] 本身就攔截了所有的路徑——前面也提到過了

最終效果如下:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88739.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88739.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88739.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TCP 傳輸時 sk_buff 的 clone 和 unclone

周一有位朋友咨詢個問題&#xff0c;問題本身不重要&#xff0c;但牽扯出的細節卻是非常有趣。 Linux 內核協議棧的 skb 設計非常高效和精巧&#xff0c;多個 skb 可以指向同一塊 data&#xff0c;這就是 clone&#xff0c;當 data 不止一個 skb 指示時&#xff0c;任何一個 s…

【51單片機】51單片機學習筆記-課程簡介

00. 目錄 文章目錄00. 目錄01. 學習哪種類型的單片機02. 學習單片機方法03. 學習單片機硬件設備04. 學習單片機軟件設備05. 學完單片機能做什么06. 附錄01. 學習哪種類型的單片機 單片機的型號那么多&#xff0c;該如何選擇一款合適的進行學習呢&#xff1f;這里給讀者首推的當…

【Docker基礎】Docker端口映射(-p參數)深度解析與實踐指南

目錄 前言 1 Docker網絡基礎 1.1 Docker網絡模型概述 1.2 容器網絡隔離性 2 端口映射基礎 2.1 端口映射概念 2.2 為什么需要端口映射 3 -p參數詳解 3.1 基本語法 3.2 四種映射格式 3.2.1 完整格式 3.2.2 省略宿主機IP 3.2.3 隨機宿主機端口 3.2.4 指定協議類型 …

2、鴻蒙Harmony Next開發:ArkTS語言

目錄 什么是ArkTS&#xff1f; ArkTS的發展趨勢 ArkTS的定位及約束 ArkTS的對UI的拓展 1、UI描述 2、狀態管理&#xff1a; ArkTS語法基礎 基本知識&#xff1a;聲明 基本知識&#xff1a;類型 基本知識&#xff1a;空安全 基本知識&#xff1a;類型安全與類型推斷 …

【Elasticsearch】function_score

如果你希望在 Elasticsearch 查詢中降低某些特定 `id` 的文檔評分,可以通過 `function_score` 查詢結合 `script_score` 函數來實現。`script_score` 允許你使用自定義腳本對文檔的評分進行調整。 以下是一個示例,展示如何降低某些特定 `id` 的文檔評分: 示例場景 假設我們…

vscode打開stm32CubeIDE的項目的注釋問題

文章目錄 目的是為消除紅色底線打開命令面板&#xff1a;CtrlShiftP 搜索并打開&#xff1a;C/C: Edit Configurations (JSON) 修改并添加。&#xff08;注意里面的版本號&#xff09; {"configurations": [{"name": "Win32","includePath&…

ESP32使用freertos更新lvgl控件內容

LVGL不是線程安全&#xff0c;所有 lv_xxx方法只能在GUI主線程調用。 freertos都是線程池&#xff0c;子線程&#xff0c;不能直接更新lvgl&#xff0c;不然看門狗被觸發&#xff0c;死機。 推薦方法案例&#xff1a; 假如搜索wifi列表得到參數是wifi_options&#xff0c;需要通…

OBOO鷗柏丨滿天星(MTSTAR)多媒體信息發布系統技術解析

初次啟動歡迎您使用鷗柏(OBOO)滿天星(MTSTAR)多媒體信息發布系統&#xff0c;在使用本系統的獨立服務器模式前&#xff0c;我們需要完成設備的一些必須設置教程技術說明。其總體流程分為兩步&#xff1a;錄入本地服務器IP地址->連接網絡您獲取到的OBOO鷗柏滿天星(MTSTAR)液晶…

數據結構:棧、隊列、鏈表

目錄 棧 ?隊列 鏈表 棧 棧數據結構特點&#xff1a;先入棧的數據后出&#xff0c;此數據結構常用的方法有&#xff1a;入棧push、出棧pop、查看棧頂元素peek等&#xff0c;下方示例以數組實現棧結構。 package com.ginko.datastructure; import lombok.extern.slf4j.Slf4j…

Python-難點-uinttest

1 需求要求&#xff1a;unittest.TestCase放在列表中&#xff0c;列表存儲的是腳本文件名import使用動態加載方式&#xff1a;importlib.import_module()unittest.TestLoader使用loadTestsFromModule()2 接口3 示例4 參考資料

開源 python 應用 開發(五)python opencv之目標檢測

最近有個項目需要做視覺自動化處理的工具&#xff0c;最后選用的軟件為python&#xff0c;剛好這個機會進行系統學習。短時間學習&#xff0c;需要快速開發&#xff0c;所以記錄要點步驟&#xff0c;防止忘記。 鏈接&#xff1a; 開源 python 應用 開發&#xff08;一&#xf…

ABP VNext + OpenTelemetry + Jaeger:分布式追蹤與調用鏈可視化

ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680; &#x1f4da; 目錄ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680;背景與動機 &#x1f31f;環境與依賴 &#x1f4e6;必裝 NuGet 包系統架構概覽…

C語言中整數編碼方式(原碼、反碼、補碼)

在 C 語言中&#xff0c;原碼、反碼、補碼的運算規則與其編碼特性密切相關&#xff0c;核心差異體現在符號位是否參與運算、進位如何處理以及減法是否能轉化為加法等方面。以下是三者的運算規則及特點分析&#xff08;以 8 位整數為例&#xff0c;符號位為最高位&#xff09;&a…

js二維數組如何變為一維數組

在 JavaScript 中&#xff0c;將二維數組轉換為一維數組&#xff08;扁平化&#xff09;有多種方法&#xff0c;可根據數組結構復雜度、性能需求和兼容性選擇。以下是最常用的實現方式&#xff1a; 1. 使用 flat() 方法&#xff08;ES2019&#xff09; MDN釋義&#xff1a;flat…

Claude code在Windows上的配置流程

前言 昨天在服務器上配置好了 Claude code&#xff0c;發現其編碼性能和效率都非常不錯。 然而&#xff0c;嘗試用它修改帶 UI 界面的客戶端程序時頗為不便&#xff0c;因為服務器沒有圖形化界面&#xff0c;無法直接將應用界面直接顯示到開發機上&#xff0c;調試起來頗為不…

手把手教你用YOLOv10打造智能垃圾檢測系統

無需編程基礎&#xff01;手把手教你用YOLOv10打造智能垃圾檢測系統 垃圾分類不再難&#xff0c;AI助手秒識別 你是否曾站在分類垃圾桶前猶豫不決&#xff1f;塑料瓶是可回收還是其他垃圾&#xff1f;外賣餐盒到底該丟哪里&#xff1f;隨著垃圾分類政策推廣&#xff0c;這樣的困…

batchnorm類

1. 偽代碼&#xff1a;2. python代碼&#xff1a;3. 測試&#xff1a;4. 加深理解&#xff1a;以 為例&#xff0c;x3&#xff0c;可見輸出的batchnorm后y0.2627.查看模型記錄的均值及方差&#xff0c;計算y0.286799&#xff0c;理解是大致這樣的計算過程。&#xff08;為什么數…

SpringBoot項目保證接口冪等的五種方法!

1. 冪等概述 1.1 深入理解冪等性 在計算機領域中&#xff0c;冪等&#xff08;Idempotence&#xff09;是指任意一個操作的多次執行總是能獲得相同的結果&#xff0c;不會對系統狀態產生額外影響。在Java后端開發中&#xff0c;冪等性的實現通常通過確保方法或服務調用的結果…

SQL新手入門詳細教程和應用實例

SQL(Structured Query Language)是用于管理和操作關系型數據庫的標準語言。它允許你創建、查詢、更新和刪除數據。本教程將從基礎概念開始,逐步引導你上手SQL,并提供詳細的應用實例。教程基于標準SQL語法,實際使用時需根據數據庫系統(如MySQL、SQLite或PostgreSQL)調整。…

DVWA-LOW級-SQL手工注入漏洞測試(MySQL數據庫)+sqlmap自動化注入-小白必看(超詳細)

首次使用DVWA的靶場&#xff0c;咋們先從最低級別的LOW開始&#xff0c;因為之前玩過一下墨者學院&#xff0c;對sql注入有一點認識和理解&#xff0c;所以先從sql的盲注開始&#xff1b; 1、測試注入點是否存在sql注入的漏洞&#xff1b; &#xff08;1&#xff09;首先我們…