Next.js 页面和路由
Next.js 页面与路由学习笔记Next.js 13 的 App Router 基于文件系统路由通过文件夹和文件的命名约定自动生成路由无需手动配置路由表。1. 基本路由规则1.1 核心约定文件作用是否必须page.tsx定义路由的 UI页面内容是没有则该路径不可访问layout.tsx定义路由的布局包裹子页面否根布局必须loading.tsx定义加载状态否error.tsx定义错误边界否not-found.tsx定义 404 页面否template.tsx类似 layout但导航时重新挂载否1.2 文件夹 路由段app/ ├── page.tsx → / ├── about/ │ └── page.tsx → /about ├── blog/ │ └── page.tsx → /blog │ └── [slug]/ │ └── page.tsx → /blog/hello-world ├── dashboard/ │ ├── page.tsx → /dashboard │ └── settings/ │ └── page.tsx → /dashboard/settings关键规则只有存在page.tsx的文件夹才是可访问的路由。文件夹名即为 URL 路径段。page.tsx默认导出的组件就是该路由的页面内容。2. 动态路由2.1 基本动态路由[slug]用方括号包裹的文件夹名表示动态参数。app/ └── blog/ └── [slug]/ └── page.tsx → /blog/任意字符串// app/blog/[slug]/page.tsx export default function BlogPost({ params }: { params: { slug: string } }) { return h1文章: {params.slug}/h1; } // 访问 /blog/hello-world → 显示 文章: hello-world // 访问 /blog/nextjs-13 → 显示 文章: nextjs-132.2 多段动态路由app/ └── shop/ └── [category]/ └── [id]/ └── page.tsx → /shop/shoes/nike-001// app/shop/[category]/[id]/page.tsx export default function Product({ params, }: { params: { category: string; id: string }; }) { return ( div p分类: {params.category}/p p商品: {params.id}/p /div ); }2.3 捕获所有路由[...slug]用[...slug]匹配剩余所有路径段至少匹配一段。app/ └── docs/ └── [...slug]/ └── page.tsxURLparams.slug/docs/getting-started[getting-started]/docs/api/reference[api, reference]/docs/guide/advanced/config[guide, advanced, config]/docs404至少需要一段2.4 可选捕获所有路由[[...slug]]用双括号表示可选0 段或多段都匹配。app/ └── docs/ └── [[...slug]]/ └── page.tsxURLparams.slug/docsundefined或[]/docs/getting-started[getting-started]/docs/api/reference[api, reference]3. 布局系统 (Layout)3.1 根布局 (Root Layout)必须存在定义在app/layout.tsx包含html和body标签。// app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( html langzh body header全局导航栏/header main{children}/main footer全局页脚/footer /body /html ); }3.2 嵌套布局 (Nested Layout)子目录中的layout.tsx会嵌套在父布局内不会替换父布局。app/ ├── layout.tsx ← 根布局全局导航 页脚 ├── page.tsx ← 首页 └── dashboard/ ├── layout.tsx ← 仪表盘布局侧边栏 ├── page.tsx ← /dashboard └── settings/ └── page.tsx ← /dashboard/settings// app/dashboard/layout.tsx export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { return ( div style{{ display: flex }} nav侧边栏概览 | 设置 | 消息/nav section{children}/section /div ); }渲染结果访问/dashboard/settingshtml body header全局导航栏/header ← 根布局 main div styledisplay:flex nav侧边栏/nav ← dashboard 布局 section h1设置页面/h1 ← settings/page.tsx /section /div /main /body /html3.3 layout vs template特性layout.tsxtemplate.tsx实例共享导航时不重新创建独立每次导航重新挂载状态保持保持跨页面共享状态不保持每次重置性能更好不重新挂载稍差适用场景通用布局进入/离开动画、useEffect每次执行4. 特殊文件详解4.1loading.tsx— 加载状态利用 React Suspense在页面数据加载时自动显示。// app/dashboard/loading.tsx export default function Loading() { return ( div classNameflex items-center justify-center h-screen div classNameanimate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 / /div ); }效果访问/dashboard时如果页面组件正在获取数据先显示 loading 动画数据就绪后自动替换为真实内容。4.2error.tsx— 错误边界捕获当前路由段的运行时错误显示友好的错误提示。// app/error.tsx use client; // 必须是客户端组件 export default function Error({ error, reset, }: { error: Error { digest?: string }; reset: () void; }) { return ( div h2出错了/h2 p{error.message}/p button onClick{() reset()}重试/button /div ); }4.3not-found.tsx— 404 页面当调用notFound()函数或路径不存在时显示。// app/not-found.tsx export default function NotFound() { return ( div h1404/h1 p页面不存在/p a href/返回首页/a /div ); } // 在页面中主动触发 import { notFound } from next/navigation; export default async function Post({ params }) { const post await getPost(params.slug); if (!post) { notFound(); // 触发 not-found.tsx } return article{post.title}/article; }4.4route.ts— API 路由处理 HTTP 请求替代传统的 Express 路由。// app/api/users/route.ts import { NextResponse } from next/server; // GET /api/users export async function GET() { const users await db.user.findMany(); return NextResponse.json(users); } // POST /api/users export async function POST(request: Request) { const body await request.json(); const user await db.user.create({ data: body }); return NextResponse.json(user, { status: 201 }); }5. 路由组 (Route Groups)用圆括号(groupName)创建的文件夹不影响 URL仅用于组织代码。5.1 按布局分组app/ ├── (auth)/ │ ├── layout.tsx ← 登录/注册专用布局无导航栏 │ ├── login/ │ │ └── page.tsx → /login │ └── register/ │ └── page.tsx → /register ├── (marketing)/ │ ├── layout.tsx ← 营销页面布局有导航栏 页脚 │ ├── page.tsx → / │ └── about/ │ └── page.tsx → /about/login和/register共享(auth)布局无导航栏。/和/about共享(marketing)布局有导航栏。URL 中不包含(auth)或(marketing)。5.2 按团队/功能分组app/ ├── (admin)/ │ ├── dashboard/ │ │ └── page.tsx → /dashboard │ └── users/ │ └── page.tsx → /users └── (store)/ ├── products/ │ └── page.tsx → /products └── cart/ └── page.tsx → /cart6. 平行路由 (Parallel Routes)用slotName命名的文件夹允许在同一布局中同时渲染多个页面。6.1 结构app/ ├── layout.tsx ← 引用 analytics 和 team ├── analytics/ │ └── page.tsx ← 分析面板 ├── team/ │ └── page.tsx ← 团队面板 └── page.tsx ← 主内容6.2 布局接收 slots// app/layout.tsx export default function Layout({ children, analytics, team, }: { children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode; }) { return ( div style{{ display: grid, gridTemplateColumns: 1fr 1fr 1fr }} div{analytics}/div div{children}/div div{team}/div /div ); }6.3 典型场景模态框app/ ├── layout.tsx ├── modal/ │ ├── (.)login/ ← 拦截 /login在模态框中显示 │ │ └── page.tsx │ └── default.tsx ← 模态框未激活时显示 null ├── login/ │ └── page.tsx ← 直接访问 /login 的完整页面 └── page.tsx// app/modal/default.tsx export default function Default() { return null; // 模态框未激活时不显示 } // app/layout.tsx export default function Layout({ children, modal }) { return ( {children} {modal} / ); }7. 拦截路由 (Intercepting Routes)在不改变 URL 的情况下用另一个路由的内容渲染当前页面。7.1 命名约定约定含义(.)folder拦截同级的 folder 路由(..)folder拦截上一级的 folder 路由(..)(..)folder拦截上两级的 folder 路由(...)folder拦截根目录的 folder 路由7.2 典型场景照片弹窗app/ ├── layout.tsx ├── modal/ │ └── (.)photo/ │ └── [id]/ │ └── page.tsx ← 弹窗版本从照片列表点击进入 ├── photo/ │ └── [id]/ │ └── page.tsx ← 完整页面版本直接访问 URL └── page.tsx ← 照片列表从列表点击URL 变为/photo/123但显示弹窗拦截路由生效。直接访问/photo/123显示完整页面拦截路由不生效。刷新/photo/123显示完整页面刷新不走客户端导航。8. 页面导航8.1Link组件import Link from next/link; export default function Nav() { return ( nav Link href/首页/Link Link href/about关于/Link Link href/blog/hello-world文章/Link {/* 动态链接 */} Link href{/blog/${post.slug}}{post.title}/Link {/* 激活状态 */} Link href/about scroll{false}关于不滚动到顶部/Link /nav ); }特性视口内的Link自动预取prefetch目标页面。客户端导航不会整页刷新。自动滚动到页面顶部可关闭。8.2 编程式导航use client; import { useRouter } from next/navigation; export default function LoginForm() { const router useRouter(); async function handleSubmit() { const success await login(); if (success) { router.push(/dashboard); // 导航到 /dashboard router.refresh(); // 刷新当前路由重新获取数据 } } return button onClick{handleSubmit}登录/button; }方法作用router.push(href)导航到新页面加入历史记录router.replace(href)替换当前页面不加入历史记录router.back()返回上一页router.refresh()刷新当前路由重新获取服务端数据8.3 导航行为对比行为Linkrouter.pusha标签客户端导航是是否整页刷新预取是否否历史记录加入加入加入适用场景大多数导航表单提交后跳转外部链接9. 路由元数据 (Metadata)9.1 静态元数据// app/about/page.tsx export const metadata { title: 关于我们, description: 这是关于我们的页面, }; export default function AboutPage() { return h1关于我们/h1; }9.2 动态元数据// app/blog/[slug]/page.tsx export async function generateMetadata({ params }) { const post await getPost(params.slug); return { title: post.title, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, images: [post.coverImage], }, }; }9.3 布局中的元数据继承子页面的title会覆盖父布局的title也可用模板语法// app/layout.tsx export const metadata { title: { default: 我的网站, // 默认标题 template: %s | 我的网站, // 子页面标题模板 }, }; // app/about/page.tsx export const metadata { title: 关于我们, // 最终: 关于我们 | 我的网站 };10. 完整路由速查表文件/文件夹URL说明app/page.tsx/首页app/about/page.tsx/about静态路由app/blog/[slug]/page.tsx/blog/hello动态路由app/shop/[...slug]/page.tsx/shop/a/b/c捕获所有路由app/docs/[[...slug]]/page.tsx/docs或/docs/a/b可选捕获所有路由app/(auth)/login/page.tsx/login路由组不影响 URLapp/modal/(.)photo/[id]/page.tsx/photo/123平行路由 拦截路由app/api/users/route.ts/api/usersAPI 路由总结Next.js 路由系统的核心记忆点文件即路由page.tsx定义可访问路径文件夹名即 URL 段动态参数[slug]单段、[...slug]多段、[[...slug]]可选多段布局嵌套layout.tsx从根到叶逐层嵌套导航时不销毁路由组(group)组织代码不影响 URL平行路由slot同一布局渲染多个独立视图拦截路由(.)在客户端导航时拦截直接访问不拦截特殊文件loading/error/not-found处理非理想状态
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2602237.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!