【Next.js 入门教程系列】03-路由与跳转

news2024/10/12 5:52:37

原文链接

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的库点个star,关注一下吧

上一篇【Next.js 入门教程系列】02-风格化

路由与跳转

本篇包括以下内容:

  • Define dynamic routes
  • Access route and query string parameters
  • Create layouts
  • Show loding UIs
  • Handle errors

Routing Overview​

本节代码链接

前面提到过,在 Next.js 中,依据约定俗成每个文件夹中仅有 page.tsx 会被渲染为公共页面。除此之外 Next.js 中还有其他特殊命名规则文件:

  • page.tsx
  • layout.tsx
  • loading.tsx
  • route.tsx
  • not-found.tsx
  • error.tsx

随着某个 page 里包含的内容越来越多,可以将其中的部分内容封装为 component,并放在同目录下(而不需要放到 component 文件夹里),以提高结构的可读性和效率

动态路由​

本节代码链接

Typescript 可以直接使用{}解构

interface Props {
  id: num;
}
// 可以像 {id}: Props 这样,直接解构出来
const UserDetailPage = ({ id }: Props) => {
  return <div>UserDetailPage</div>;
};

动态路由,即可以根据参数进行自动适配的路由。比如想要实现 user/userid 这样的路由,就可以使用下面的这种形式。使用中括号将所需的文件夹名包起来

users
  │  page.tsx
  │  UserTable.tsx
  │
  └─[id]
          page.tsx

users/[id]/page.tsx

import React from "react";

interface Props {
  params: { id: number };
}

const UserDetailPage = ({ params: { id } }: Props) => {
  return <div>UserDetailPage {id}</div>;
};

export default UserDetailPage;

这样即可使用 localhost:5050/users/1 这样的形式来访问

Dynamic Routing

嵌套动态路由​

比如我们想要实现 users/userid/photos/photoid 这样的路由,其中 userid 和 photoid 为变量,也就是实现动态路由的嵌套。我们仍然可以使用中括号将两个文件夹都括起来,就像如下的文件结构。

users
│  page.tsx
│  UserTable.tsx
│
└─[id]
    │  page.tsx
    │
    └─photos
        └─[photoId]
                page.tsx

需要注意的是,变量名(文件夹名,上结构中的 id 和 photoId)不能重复,因为我们在使用时需要直接使用这个变量名来访问

# users/[id]/photos/[photoId]/page.tsx
import React from "react";

interface Props {
  // 这里的 id 和 photoId 都要和前面文件夹名对应上,才能正确获取到
  params: { id: number, photoId: number };
}

// 同样可以直接解构使用
const PhotoPage = ({ params: { id, photoId } }: Props) => {
  return (
    <div>
      User {id}'s Photo {photoId}
    </div>
  );
};

export default PhotoPage;

以此类推,不管有多少层都可以正常嵌套,注意命名别重复即可

Nested Dynamic Routing

Catch-all 路由​

本节代码链接

在 Next.js 中,使用 [...slug] 来捕捉多层并生成动态路由的功能被称为 Catch-All 路由。它允许你在路径中捕捉任意数量的片段,并将它们作为参数传递给页面组件

上文说到可以将文件夹名字用 [] 包起来来生成动态路由,如果在名字前添加 ... ,比如 [...slug] 即可生成一个 Catch-All 路由。他的作用是,无论你加多少层路由都可以识别出来。比如一般来说,想要实现 products/grocery/dairy/milk 这个路由,需要一层一层创建,而使用 Catch-all 路由,只需要创建 products/[...slug]/page.tsx 即可。

products/[...slug]/page.tsx

import React from "react";

interface Props {
  // 注意这里的的 slug 类型为 string 数组
  params: { slug: string[] };
}

const ProductPage = ({ params: { slug } }: Props) => {
  return <div>ProductPage {slug && slug.map((str) => str + " ")}</div>;
};

export default ProductPage;

最终呈现的效果如下:

Catch-All Routing

在该例子中,products 文件夹下仅有 [...slug] 文件夹,并不存在 page.tsx ,即直接访问 localhost:5050/products 会报 404。如果想要这个 slug 为空也可以正常访问,则可以使用 [[...slug]] 的形式命名文件夹,即再套一层中括号。即如下文件层级:

products
└─[[...slug]]
        page.tsx

最终效果如下

Catch-All 在多层级的 tag 或者类别中导航十分好用,只需要在页面中提取出 slug 再去数据库获取,渲染即可

获取参数​

本节代码链接

我们经常会在 url 中添加一些参数,比如 users?sortOrder=name 这样,用于排序之类的操作。在 Next.js 中也比较简单,这里给一个例子

import React from "react";
import UserTable from "./UserTable";

// 首先在这里添加可选的参数,比如这里添加一个sortOrder
interface Props {
  searchParams: { sortOrder: string };
}

const UserPage = async ({ searchParams: { sortOrder } }: Props) => {
  return (
    <>
      <h1>User</h1>
      <UserTable sortOrder={sortOrder} />
    </>
  );
};

export default UserPage;

这样就可以通过 localhost:5050/users?sortOrder=name 来传参。

实现排序逻辑(与本章主线无关)

接下来是实现排序的逻辑,首先安装 fast-sort npm

npm i fast-sort

然后在 users/Usertable.tsx 中添加逻辑

import Link from "next/link";
import React from "react";
// 引入 fast-sort
import { sort } from "fast-sort";

interface User {
  id: number;
  name: string;
  email: string;
}

interface Props {
  sortOrder: string;
}

const UserTable = async ({ sortOrder }: Props) => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  const users: User[] = await res.json();

  // 排序
  const sortedUsers = sort(users).asc(
    sortOrder === "email" ? (user) => user.email : (user) => user.name
  );
  return (
    <>
      <table className="table table-bordered">
        <thead>
          <tr>
            <th>
              <Link href="/users?sortOrder=name">Name</Link>
            </th>
            <th>
              <Link href="/users?sortOrder=email">Email</Link>
            </th>
          </tr>
        </thead>
        <tbody>
          {sortedUsers.map((user) => (
            <tr key={user.id}>
              <td>{user.name}</td>
              <td>{user.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
};


布局​

本节代码链接

上文说到,Next.js 中的 layout.tsx 也是特殊文件,用于布局页面,在某个文件夹下的 layout.tsx 会将其布局应用到其所有的子文件夹中。比如想要在每个页面都添加 NavBar 和 Footer,则只需要根文件夹下的 layout.tsx 中添加即可。用此方法可以减少大量的冗余代码,提升易读性,也更便于后期修改

为某个文件夹创建新的 layout 可以用如下的形式

import React, { ReactNode } from "react";

interface Props {
  // layout 固定需要一个 NeactNode 类型的 children 为参数
  children: ReactNode;
}

const AdminLayout = ({ children }: Props) => {
  return (
    <div className="flex">
      {/* 这里是 NavBar */}
      <aside className="bg-slate-200 p-5 mr-5">Admin Sidebar</aside> <div>
        {children}
      </div> {/* 渲染 children */}
      {/* 这里还可以加 Footer*/}
    </div>
  );
};

export default AdminLayout;

这里是根目录下的 layout 文件,相对会多一些 metadata 之类的内容,无伤大雅,其内容和上面差不多,只不过直接都写到了 export 里面,略掉了格式声明

import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import NavBar from "./NavBar";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    // 主要修改的还是这部分
    <html lang="en" data-theme="winter">
      <body className={inter.className}>
        <NavBar />
        <main className="p-5">{children}</main>
      </body>
    </html>
  );
}

除此之外,在 global.css 中还可以修改对应 tag 的 style

/*  tailwind 有三个不同层级的样式,作用于不同的域 */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --foreground-rgb: 0, 0, 0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
  }
}

body {
  color: rgb(var(--foreground-rgb));
}

/*  可以通过@layer base来修改不同层级的样式 */
@layer base {
  h1 {
    /*  修改全局 h1 的样式 */
    @apply font-extrabold text-2xl mb-3;
  }
}

导航​

本节代码链接

Link 标签有以下三个特点

  • 只下载目标页面的内容(不会重复下载 css, tsx, js 等内容)
  • 会预先下载所有 Link 的内容(仅在部署环境中)
  • 在客户端缓存页面

程序性导航​

比如在需要提交表单后跳转的场景,就需要用到 Button 来跳转,这个时候就要用到程序性导航,下面是一个示例。首先需要将该组件变为客户端组件(因为要获取客户端信息),然后使用 next/navigation 库中的 router 即可实现跳转

"use client"; // 改为客户端组件
// 引入 router,注意是 next/navigation 不是 next/router
import { useRouter } from "next/navigation";
import React from "react";

const NewUserPage = () => {
  // 初始化 router
  const router = useRouter();

  return (
    <>
      <div>NewUserPage</div>
      {/* 直接使用router.push() */}
      <button className="btn btn-primary" onClick={() => router.push("/users")}>
        Create
      </button>
    </>
  );
};

export default NewUserPage;

展示一个 Loading 的 UI​

本节代码链接

展示一个 loading 的 UI 很简单,如果只有部分需要加载的组件需要这个功能,只需要拿 Suspense tag 把它包起来即可,其中 fallback 则是加载中显示的内容

// 需要 import 的
import React, { Suspense } from "react";

<Suspense fallback={<p>Loading...</p>}>
  <UserTable sortOrder={sortOrder} />
</Suspense>;

上文我们说到 loading.tsx 也是特殊文件,如果创建该文件,将直接应用于所有的子文件夹,比如下面这样。并且 daisyUI Loading 也有对应的样式

import React from "react";

const Loading = () => {
  return (
    <div className="place-content-center">
      {/* 也有现成的 daisyUI 类可以使用 */}
      <span className="loading loading-infinity loading-lg"></span>
    </div>
  );
};

export default Loading;

除此之外,如下图所示使用 chorme 的 React Devtool ,先选中 suspense 标签,然后可以在这里暂停这个网页,从而看到 suspend tag 里面的内容,也就是渲染时用户看到的内容。更方便于测试

Suspending

处理 Error​

本节代码链接

404 not Found​

在文件夹中添加 not-found.tsx 即可(同理也是特殊文件,命名必须一字不差)。也和 layout 那些一样,某个文件夹内的会作用到所有子文件夹内,除非这个文件夹内有自己的 not-found.tsx,即可以针对不同的区域设置不同的 404。如 想访问一个不存在的用户和不存在的商品,就可以显示不同的内容

import React from "react";

const NotFoundPage = () => {
  return <div>The requested page doesn&apos;t exist</div>;
};

export default NotFoundPage;

效果如下:

404

其他 Error​

其他可能出现的错误就是代码错误了,一些 502、 runtime error 这类的。同样的,添加 error.tsx 来捕捉这类问题

"use client";
import React from "react";

interface Props {
  error: Error;
  reset: () => void;
}

const ErrorPage = ({ error, reset }: Props) => {
  // 可以打印错误到本地日志,这里的是打印到用户端的 console
  console.log("Error", error);
  return (
    <>
      <div>An unexpected error has occurred</div>
      {/* 可以添加一个重试的 button */}
      <button className="btn" onClick={() => reset()}>
        Retry
      </button>
    </>
  );
};

export default ErrorPage;

效果如下:

Other Error

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的库点个star,关注一下吧

下一篇讲 API 的构造

下一篇【Next.js 入门教程系列】04-构造 API

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2207171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

构建高效作业管理平台:Spring Boot师生协作评审系统

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

23年408数据结构

第一题&#xff1a; 解析&#xff1a; 第一点&#xff0c;我们要知道顺序存储的特点&#xff1a;优点就是随用随取&#xff0c;就是你想要查询第几个元素可以直接查询出来&#xff0c;时间复杂度就是O(1)&#xff0c;缺点就是不适合删除和插入&#xff0c;因为每次删除和插入一…

Pyramid-Flow – 北大、快手、北邮联合开源的视频生成模型

Pyramid-Flow是什么 Pyramid-Flow是一种先进的视频生成模型&#xff0c;由北京大学、快手科技和北京邮电大学的研究人员联合推出。模型根据文本提示生成长达10秒、分辨率高达1280768、帧率24帧每秒的高清视频。Pyramid-Flow的核心为创新的金字塔流匹配算法&#xff0c;算法将视…

为您的人工智能数据提供类似 Git 的版本管理功能

您过去肯定有过版本控制代码。但是&#xff0c;您是否对数据进行了版本控制&#xff1f;您是否曾经想过与不同的团队协作处理大量数据&#xff0c;而无需提交大量数据&#xff1f;想象一下&#xff0c;使用类似 git 的命令来运行类似存储库的生态系统&#xff0c;在该生态系统中…

模型案例:| SenseCraft Al平台模型训练与部署

导读 2023年以ChatGPT为代表的大语言模型横空出世&#xff0c;它的出现标志着自然语言处理领域取得了重大突破。它在文本生成、对话系统和语言理解等方面展现出了强大的能力&#xff0c;为人工智能技术的发展开辟了新的可能性。同时&#xff0c;人工智能技术正在进入各种应用领…

关于Transformer的相关问题

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; &#x1f50d;【大厂面试真题】系列&#xff0c;带你攻克大厂面试真题&#xff0c;秒变offer收割机&#xff01; ❓今日问题&am…

MacOS 同时配置github、gitee和gitlab密钥

MacOS 同时配置github、gitee和gitlab密钥 1 在终端中新建 ~/.ssh目录 1.1 生成GitHub、Gitee和Gitlab的SSH密钥对 ssh-keygen -t ed25519 -C "xxxxxxxxxxx.com" -f ~/.ssh/id_ed25519_gitee ssh-keygen -t ed25519 -C "xxxxxxxxxxx.com" -f ~/.ssh/id_…

mac电脑卸载软件在哪里?苹果电脑卸载软件的正确步骤

如今&#xff0c;越来越多的人选择使用Mac电脑来办公&#xff0c;然而&#xff0c;随着使用时间的增长&#xff0c;Mac电脑上安装的软件越来越多&#xff0c;硬盘空间逐渐被占用&#xff0c;电脑性能也可能因此受到影响。许多用户在尝试卸载不再需要的软件时&#xff0c;发现直…

【JVM】原理篇

1 栈上的数据存储 在Java中有8大基本数据类型&#xff1a; 这里的内存占用&#xff0c;指的是堆上或者数组中内存分配的空间大小&#xff0c;栈上的实现更加复杂。 以基础篇的这段代码为例&#xff1a; Java中的8大数据类型在虚拟机中的实现&#xff1a; boolean、byte、char、…

【重学 MySQL】六十四、主键约束的使用

【重学 MySQL】六十四、主键约束的使用 主键约束的特性主键约束的创建创建单列主键创建联合主键在表创建后添加主键 主键约束的删除主键约束的特点主键约束与自增长约束注意事项 在MySQL中&#xff0c;主键约束&#xff08;PRIMARY KEY&#xff09;用于唯一标识表中的每一行数据…

AOT漫谈专题(第一篇): 如何调试C# AOT程序

一&#xff1a;背景 1. 讲故事 上个月接到了二个C# AOT程序的故障分析&#xff0c;发现如今的C# AOT程序也开始在各个领域开枝散叶了&#xff0c;这是一件非常好的事情&#xff0c;本着对这类程序有一个专业的维修态度&#xff0c;开一个系列好好聊一聊吧&#xff0c;当然我这…

互动式教育技术:Spring Boot师生共评作业管理系统

3系统分析 3.1可行性分析 通过对本师生共评的作业管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本师生共评的作业管理系统采用JAVA作为开发语言&…

uniapp引入ThorUI的方法

1、下载文件 2、复制相应的文件除了pages 3、往项目中复制即可 4、引入即可实现 5、添加easycome自动引入

SpringBoot2核心功能-web开发

目录 一、简单功能分析1.1、静态资源访问1.2、欢迎页支持、自定义 Favicon 二、请求参数处理2.1、请求映射2.1.1、rest使用与原理2.1.2、请求映射原理 2.2、普通参数与基本注解2.2.1、注解2.2.2、Servlet API&#xff1a;2.2.3、复杂参数&#xff1a; 三、拦截器四、Web原生组件…

STM32之CAN外设

相信大家在学习STM32系列的单片机时&#xff0c;在翻阅芯片的数据手册时&#xff0c;都会看到这么一个寄存器外设——CAN外设寄存器。那么&#xff0c;大家知道这个外设的工作原理以及该如何使用吗&#xff1f;这节的内容将会详细介绍STM32上的CAN外设&#xff0c;文章结尾附有…

气象数据三维可视化的实现原理及代码

气象数据三维可视化是一种使用三维图形技术来呈现和分析气象数据的方法。通过三维可视化&#xff0c;用户可以更直观地观察气象数据的空间分布、变化趋势以及天气现象的复杂结构。这种技术广泛应用于气象预报、科学研究以及环境监测等领域。 本文将介绍气象数据三维可视化的基…

未来战争中的指控体系不仅是分布式的

指控体系&#xff0c;作为军事指挥与控制的重要组成部分&#xff0c;经历了从传统的集权指挥向现代分布式指挥的转变。历史上&#xff0c;战争指挥体系主要依赖于集中式指挥官的决策&#xff0c;信息流动缓慢&#xff0c;决策过程受到地理位置、通信手段等多种因素的制约。随着…

数据库实例

例3.5建立一个“学生”表student create table student(sno char(9) primary key,sname char(20) unique,ssex char(2),sage smallint,sdept char(20)); 例3.6建立一个“课程”表course create table course(cno char(4) primary key,cname char(40) not null,cpno char(4),…

保姆级教程 | VMD输出局部结构及利用TkConsole实现旋转

背景 由于课题需要,现需要展示lammps模拟轨迹中的局部结构(主要是想可视化这里的结果:保姆级教程 | 输出分子动力学轨迹文件输出特定原子范围内的化学环境),因为ovito效果有点笨笨的,所以我这里选用VMD软件为例进行操作,效果图(超级好看夸夸): (说明:主要的分子构…

计算机毕业设计 | SSM 旅游网站后台管理系统(附源码)

1&#xff0c;概述 1.1 背景分析 随着人们生活水平的提高和对休闲旅游的日益重视&#xff0c;旅游业已成为全球最大的经济产业之一。越来越多的人选择通过在线方式进行旅行预订&#xff0c;这种趋势为旅游网站提供了巨大的商机。用户体验是决定旅游网站成功与否的关键因素。良…