关于我 壹文 项目 三五好友
Next.js 学习笔记 📚 2024-10-04
文章摘要

Next.js 学习笔记 📚

欢迎来到我的 Next.js 学习笔记!这里记录了我在学习和开发过程中遇到的重要概念、工具和代码示例。希望对你有所帮助!😊


目录 📑

  1. 依赖包介绍
  2. TypeScript 特性
  3. React Query 使用示例
  4. 组件封装
  5. 主题切换
  6. 身份验证
  7. Prisma 使用
  8. Next.js 配置
  9. 受保护的路由
  10. 其他知识点
  11. 动画与图标

依赖包介绍 🛠️

以下是我项目中使用的主要依赖包及其简要说明:

  • @clerk/backend@clerk/nextjs:Clerk 是用于身份验证和用户管理的服务,这些包提供了 Next.js 应用程序的后端和前端集成。
  • @neondatabase/serverless:Neon 是一个云原生的 PostgreSQL 数据库,这个包用于在 Serverless 环境中与 Neon 数据库交互。
  • @tiptap/pm, @tiptap/react, @tiptap/starter-kit:Tiptap 是一个用于 React 的富文本编辑器框架,这些包提供了核心库、React 集成和一个入门工具包。
  • drizzle-kitdrizzle-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! 中的 ! 是一个非空断言操作符,告诉编译器该变量在运行时不会是 nullundefined

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 类型中选择 nameimageemail 字段。

使用示例

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 数组中的值分别赋给 courseIdunitIndexParamchapterIndexParam
  • 使用 parseInt 将字符串索引转换为整数,以便在后续代码中使用。

类型合并

type Props = {
  course: Course & {
    units: (Unit & {
      chapters: Chapters[];
    })[];
  };
};

说明

  • courseProps 类型中的一个属性,表示一个课程对象。
  • 通过 & 运算符,将 Course 类型与包含 units 属性的新对象类型合并,形成一个更复杂的类型。

使用 Record 类型

const [state, setState] = React.useState<Record<string, string>>({});

说明

  • Record<string, string> 表示一个对象,其属性名和属性值都是字符串类型。
  • 初始化状态为一个空对象 {}

动画与图标 🎬🔠

动画库

  • Storyset:一个提供免费插图和动画的资源网站,适合丰富项目的视觉效果。

控制图标的大小

<ArrowRight className="" strokeWidth={3} />

说明

  • 通过设置 classNamestrokeWidth 属性,可以控制图标的大小和线条粗细。

总结 🏁

以上是我在学习和使用 Next.js 过程中的一些重要笔记和示例代码。希望这些内容能帮助你更好地理解和使用 Next.js 及其周边工具。如果有任何问题或建议,欢迎交流!🚀

Not-By-AI