Next.js 学习笔记 📚
欢迎来到我的 Next.js 学习笔记!这里记录了我在学习和开发过程中遇到的重要概念、工具和代码示例。希望对你有所帮助!😊
目录 📑
依赖包介绍 🛠️
以下是我项目中使用的主要依赖包及其简要说明:
@clerk/backend
和@clerk/nextjs
:Clerk 是用于身份验证和用户管理的服务,这些包提供了 Next.js 应用程序的后端和前端集成。@neondatabase/serverless
:Neon 是一个云原生的 PostgreSQL 数据库,这个包用于在 Serverless 环境中与 Neon 数据库交互。@tiptap/pm
,@tiptap/react
,@tiptap/starter-kit
:Tiptap 是一个用于 React 的富文本编辑器框架,这些包提供了核心库、React 集成和一个入门工具包。drizzle-kit
和drizzle-orm
:Drizzle 是一个用于 TypeScript 和 SQL 数据库的 ORM 工具,这些包提供了数据库集成和 ORM 功能。firebase
:Firebase 是 Google 提供的后端服务平台,这个包用于在项目中集成 Firebase。openai-edge
:用于与 OpenAI API 交互的库,可能用于 AI 相关的功能。pg
:PostgreSQL 数据库的 Node.js 驱动程序。@tanstack/react-query
:用于管理数据获取和缓存的库。它提供了一种简单的方法来处理异步数据,例如 API 请求。
TypeScript 特性 📝
satisfies Config
配置对象符合 Config 类型的结构
const config = {
// 配置内容
} satisfies Config;
非空断言操作符 !
在 TypeScript 中,process.env.DATABASE_URL!
中的 !
是一个非空断言操作符,告诉编译器该变量在运行时不会是 null
或 undefined
。
const databaseUrl: string = process.env.DATABASE_URL!;
注意:使用 !
操作符应谨慎,因为它忽略了潜在的空值检查。
React Query 使用示例 🚀
使用 useMutation
创建待办事项
假设我们在开发一个待办事项应用,允许用户创建新的待办事项。当用户点击”创建”按钮时,我们使用 useMutation
处理异步操作。
import { useMutation } from "@tanstack/react-query";
const createTodo = useMutation({
mutationFn: async (todoData) => {
// 发送请求到服务器创建待办事项
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(todoData),
headers: {
"Content-Type": "application/json",
},
});
// 处理响应并返回数据
const data = await response.json();
return data;
},
onSuccess: (createdTodo) => {
// 在成功创建待办事项后更新应用程序的状态
setTodos((prevTodos) => [...prevTodos, createdTodo]);
},
onError: (error) => {
// 处理错误情况
console.error("Failed to create todo:", error);
setError("Failed to create todo. Please try again.");
},
});
// 在组件中使用 createTodo
const handleCreateTodo = (todoData) => {
createTodo.mutate(todoData);
};
// 渲染创建待办事项的表单
return (
<form onSubmit={handleSubmit}>
{/* 表单输入字段 */}
<button onClick={() => handleCreateTodo(todoData)}>Create</button>
</form>
);
组件封装 🧩
一个称职的组件应具备以下特性:
- 组件内维护自身的数据和状态
- 组件内维护自身的事件
- 通过初始化事件(如传入数据)来初始化组件状态,激活组件
- 对外提供配置项,控制展示及具体功能
- 通过对外提供查询接口,获取组件状态
Props 类型定义
type Props = {
user: Pick<User, "name" | "image" | "email">;
};
user
属性是一个对象,使用 Pick
工具类型从 User
类型中选择 name
、image
、email
字段。
使用示例:
type User = {
id: string;
name: string;
email: string;
image: string;
role: string;
};
type UserProfile = Pick<User, "name" | "email" | "image">;
// UserProfile 类型现在是:
// {
// name: string;
// email: string;
// image: string;
// }
主题切换 🎨
providers.tsx
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SessionProvider } from "next-auth/react";
const queryClient = new QueryClient();
export function Provider({ children, ...props }: ThemeProviderProps) {
return (
<QueryClientProvider client={queryClient}>
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
{...props}
>
<SessionProvider>{children}</SessionProvider>
</NextThemesProvider>
</QueryClientProvider>
);
}
ThemeToggle.tsx
"use client";
import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ThemeToggle({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
const { setTheme } = useTheme();
return (
<div className={className} {...props}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
身份验证 🔒
Clerk 提供的组件
@clerk/nextjs
包的组件用于在 Next.js 应用程序中提供 Clerk 身份验证和用户管理功能的上下文提供者。通过将应用程序包裹在 ClerkProvider
中,可以在整个应用程序中访问 Clerk 提供的身份验证和用户管理功能。
受保护的路由
const session = await getAuthSession();
if (session?.user) {
return redirect("/dashboard");
}
以上代码示例展示了如何在服务端保护路由。当用户登录后,无法回到原始界面,且不会出现屏幕闪烁。这些代码运行在服务端,确保 API 保护也在服务端上。
Prisma 使用 🗄️
启动 Prisma Studio
在终端输入以下命令开启可视化界面:
npx prisma studio
Prisma Client 配置
generator client {
provider = "prisma-client-js"
}
使用 Prisma Client
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main(){
const allUsers = await prisma.user.findMany();
console.log(allUsers);
}
main()
.catch(e => console.error(e))
.finally(async () => {
await prisma.$disconnect();
});
Next.js 配置 ⚙️
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ["lh3.googleusercontent.com", "s3.us-west-2.amazonaws.com"],
},
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
output: "standalone",
};
module.exports = nextConfig;
配置说明
-
output: "standalone"
:- 启用独立构建,生成一个独立的输出目录,包含所有依赖,便于部署。
- 区别:不使用
standalone
时,可能需要在部署环境中安装更多的依赖,而使用standalone
可以减少部署过程中的复杂度。
-
eslint.ignoreDuringBuilds: true
:- 在构建过程中忽略 ESLint 错误,即使存在错误也继续构建。
-
typescript.ignoreBuildErrors: true
:- 忽略 TypeScript 错误,允许构建继续进行。
受保护的路由 🔐
export async function authMiddleware(req) {
const session = await getAuthSession();
if (!session?.user) {
return new NextResponse("unauthorized", { status: 401 });
}
}
上述代码示例展示了如何在中间件中保护路由,确保只有经过身份验证的用户才能访问特定资源。
其他知识点 🌟
tsrafce
是什么
tsrafce
是一个 VSCode 扩展片段(snippet),用于快速创建带有 React Function Component 和导出的 TypeScript 代码模板。
使用方法:
在 VSCode 中输入 tsrafce
,然后按下 Tab
键,即可自动生成一个带有类型定义的 React Function Component 模板。
参数传递
type Props = {
params: {
slug: string[];
};
};
const CoursePage = async ({ params: { slug } }: Props) => {
const [courseId, unitIndexParam, chapterIndexParam] = slug;
let unitIndex = parseInt(unitIndexParam);
let chapterIndex = parseInt(chapterIndexParam);
// 进一步处理
};
说明:
slug
是一个字符串数组,包含从 URL 中提取的参数。- 通过数组解构,将
slug
数组中的值分别赋给courseId
、unitIndexParam
和chapterIndexParam
。 - 使用
parseInt
将字符串索引转换为整数,以便在后续代码中使用。
类型合并
type Props = {
course: Course & {
units: (Unit & {
chapters: Chapters[];
})[];
};
};
说明:
course
是Props
类型中的一个属性,表示一个课程对象。- 通过
&
运算符,将Course
类型与包含units
属性的新对象类型合并,形成一个更复杂的类型。
使用 Record
类型
const [state, setState] = React.useState<Record<string, string>>({});
说明:
Record<string, string>
表示一个对象,其属性名和属性值都是字符串类型。- 初始化状态为一个空对象
{}
。
动画与图标 🎬🔠
动画库
- Storyset:一个提供免费插图和动画的资源网站,适合丰富项目的视觉效果。
控制图标的大小
<ArrowRight className="" strokeWidth={3} />
说明:
- 通过设置
className
和strokeWidth
属性,可以控制图标的大小和线条粗细。
总结 🏁
以上是我在学习和使用 Next.js 过程中的一些重要笔记和示例代码。希望这些内容能帮助你更好地理解和使用 Next.js 及其周边工具。如果有任何问题或建议,欢迎交流!🚀