本节摘要:Next.js 是框架,但不是全部——数据层、状态管理、UI 组件、表单、验证都需要生态配合。本节给出 Next.js 生态的选型指南:ORM(Prisma)、状态管理(Zustand/React Query)、组件库(shadcn/ui)、表单与验证,帮助你在真实项目中快速选对工具。
阅读完本节,你应当能够:
Next.js 解决路由、渲染、部署——但"数据怎么存、状态怎么管、按钮长什么样"需要生态。选型的核心原则:先有需求,再选工具;能少用一个,就少用一个。
直觉类比:Next.js 是"房子的框架",生态是"家具家电"——床(ORM)必须买,但"要不要投影仪(复杂状态库)"取决于你的需求。别为了装修而装修。
💡 关键直觉:服务器组件改变了状态管理的需求——很多"状态"其实不需要客户端状态库:数据在服务器组件里获取,交互状态用 useState 局部管理即可。只有"全局共享 + 跨组件"的状态才需要库。
npm install prisma @prisma/client npx prisma init
// prisma/schema.prisma model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) createdAt DateTime @default(now()) }
// lib/db.ts import { PrismaClient } from "@prisma/client"; const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }; export const prisma = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
// 服务器组件中直接使用 const posts = await prisma.post.findMany({ where: { published: true }, orderBy: { createdAt: "desc" }, });
Prisma 的优势:类型安全(生成的客户端类型全推断)、迁移工具(prisma migrate)、可视化 Studio。
需要"客户端取数 + 缓存 + 重试 + 失效"时(如无限滚动、实时刷新):
"use client"; import { useQuery } from "@tanstack/react-query"; export function Stats() { const { data, isLoading } = useQuery({ queryKey: ["stats"], queryFn: () => fetch("/api/stats").then((r) => r.json()), }); if (isLoading) return <div>加载中...</div>; return <div>{data?.value}</div>; }
取舍:服务器组件能取数就别用 Query;只有"客户端需要的实时/交互数据"才引入。
跨组件共享的 UI 状态(购物车、主题、用户偏好):
// store/cart.ts import { create } from "zustand"; export const useCart = create((set) => ({ items: [], add: (item) => set((s) => ({ items: [...s.items, item] })), })); // 组件中使用 const { items, add } = useCart();
取舍:只有"多组件共享且频繁变化"的状态需要全局库;组件内状态用 useState 即可。
npx shadcn@latest init npx shadcn@latest add button card dialog
import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; export default function ProfileCard() { return ( <Card> <h2>用户资料</h2> <Button>编辑</Button> </Card> ); }
shadcn/ui 的优势:代码复制进项目(完全可控)、基于 Tailwind、与 Next.js 配合好。备选:Radix UI(无样式原语)、MUI/Ant Design(企业级组件)。
npm install react-hook-form zod @hookform/resolvers
"use client"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; const schema = z.object({ email: z.string().email("邮箱格式不对"), password: z.string().min(8, "密码至少 8 位"), }); export function RegisterForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <input {...register("email")} placeholder="邮箱" /> {errors.email && <p>{errors.email.message}</p>} <input type="password" {...register("password")} placeholder="密码" /> {errors.password && <p>{errors.password.message}</p>} <button>注册</button> </form> ); }
选型速查表:
| 需求 | 推荐 | 何时可不用 |
|---|---|---|
| 数据库 | Prisma | 简单场景用 SQLite 裸查询 |
| 客户端取数 | TanStack Query | 服务器组件能取数 |
| 全局状态 | Zustand | 状态不跨组件 |
| UI 组件 | shadcn/ui | 简单项目手写 |
| 表单验证 | RHF + Zod | 简单表单用 Server Actions 校验 |
| 误区 | 现象 | 正解 |
|---|---|---|
| 什么都引库 | 依赖爆炸、体积大 | 最小引入原则 |
| 服务器组件能取数还引 Query | 双重请求 | 服务器组件优先 |
| 全局状态滥用 | 状态难追踪 | 局部 useState,全局才用库 |
| Prisma 未初始化 | 连接报错 | prisma init + migrate + generate |
| 表单验证重复实现 | 校验散落 | Zod schema 统一 |
# 初始化 Next.js + 生态 npx create-next-app@latest my-app cd my-app npm install prisma @prisma/client npx prisma init npx shadcn@latest init npm install zustand react-hook-form zod @hookform/resolvers
// 1. 定义数据模型 prisma/schema.prisma model Post { id Int @id @default(autoincrement()) title String content String? } // 2. 服务器组件取数 export default async function Home() { const posts = await prisma.post.findMany(); return posts.map((p) => <p key={p.id}>{p.title}</p>); } // 3. UI 组件 import { Button } from "@/components/ui/button";
标准技术栈成型:Prisma(数据)+ shadcn/ui(UI)+ RHF/Zod(表单)+ Zustand(全局状态,按需)——每个工具解决一类明确问题,组合成可维护的全栈项目。
| 维度 | 考察点 |
|---|---|
| 必要性 | 框架能解决吗?多一个依赖多一分风险 |
| 活跃度 | GitHub 近期提交、Issue 响应、版本更新 |
| 生态 | 文档质量、社区教程、周边工具 |
| 成本 | 学习成本、维护成本、迁移成本 |
一句话:选型的本质是"用最小依赖解决当前明确需求"——每个依赖都是长期的维护承诺,引入前三思。
问:Prisma 和直接写 SQL 怎么选?
Prisma 提供类型安全、迁移、可视化,适合大多数应用;直接 SQL 适合复杂查询/性能极致的场景。默认 Prisma,遇到瓶颈再考虑裸 SQL。
问:状态管理到底需不需要 Zustand?
服务器组件时代,多数"状态"不需要全局库:数据在服务器组件取、交互状态用 useState。只有"跨组件共享 + 频繁变化"(购物车、全局 UI)才需要 Zustand。
问:shadcn/ui 和其他组件库怎么选?
shadcn/ui 是"复制进项目"的(完全可控、无隐藏依赖),适合定制需求;Ant Design/MUI 是企业级组件库(装好即用),适合后台系统。按团队习惯选。
问:表单校验用服务端还是客户端?
两者都要:客户端(RHF + Zod)提供即时反馈,服务端(Action 内校验)是安全底线。永远信任服务端校验,客户端校验只是体验优化。
生态选型的核心是"最小依赖解决明确需求"——Prisma 管数据、shadcn/ui 管 UI、RHF+Zod 管表单、Zustand 管全局状态(按需)、TanStack Query 管客户端取数(按需)。每次引入前问"真的需要吗",能服务器解决不引客户端库。