前端开发指南
目的
全面的指南,用于现代 Next.js 15 开发,强调 Server Components、Client Components、App Router 模式、Shadcn/ui 组件、适当的文件组织和性能优化。
何时使用此技能
- 创建新组件或页面
- 构建新功能
- 获取数据(Server Components、Server Actions)
- 使用 Next.js App Router 设置路由
- 使用 Tailwind CSS 和 Shadcn/ui 组件进行样式设计
- 性能优化
- 组织前端代码
- TypeScript 最佳实践
快速开始
新组件清单
创建组件?遵循此清单:
- [ ] 确定 Server vs Client Component(默认:Server Component)
- [ ] 仅在需要时添加
"use client"指令(交互性、hooks、浏览器 API) - [ ] 使用 TypeScript 明确 prop 类型
- [ ] 从
@/components/ui导入 Shadcn/ui 组件 - [ ] 使用 Tailwind CSS 类进行样式设计
- [ ] 导入别名:
@/components、@/lib、@/hooks - [ ] 使用
cn()实用程序进行条件类 - [ ] 默认导出在底部
- [ ] 尽可能使用 Server Components 进行数据获取
新页面清单
创建页面?设置此结构:
- [ ] 创建
app/{route-name}/page.tsx用于路由 - [ ] 默认使用 Server Component
- [ ] 直接在 Server Component 中获取数据
- [ ] 为页面特定组件创建
components/目录 - [ ] 使用
loading.tsx用于加载状态 - [ ] 使用
error.tsx用于错误边界 - [ ] 导出元数据以进行 SEO
导入别名快速参考
| 别名 | 解析为 | 示例 |
|---|---|---|
@/ |
项目根目录 | import { cn } from '@/lib/utils' |
@/components |
components/ |
import { Button } from '@/components/ui/button' |
@/lib |
lib/ |
import { cn } from '@/lib/utils' |
@/hooks |
hooks/ |
import { useMobile } from '@/hooks/use-mobile' |
@/app |
app/ |
import { Metadata } from 'next' |
定义在:tsconfig.json 路径配置
常用导入备忘单
// Next.js
import { Metadata } from 'next'
import { Suspense } from 'react'
import { notFound, redirect } from 'next/navigation'
// React (仅限 Client Components)
;('use client')
import { useState, useCallback, useMemo } from 'react'
// Shadcn/ui 组件
import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
// 实用工具
import { cn } from '@/lib/utils'
// Hooks (仅限 Client Components)
import { useMobile } from '@/hooks/use-mobile'
// 类型
import type { ComponentProps } from 'react'
主题指南
🎨 组件模式
Server Components vs Client Components:
- Server Components(默认):没有
"use client",可以直接获取数据,更小的捆绑包 - Client Components:添加
"use client"用于交互性、hooks、浏览器 API
关键概念:
- 默认使用 Server Components
- 仅在必要时使用 Client Components
- 使用 Shadcn/ui 组件(已经是 Client Components)
- 组件结构:Props → 数据获取 → 渲染 → 导出
示例 Server Component:
// app/features/posts/components/PostList.tsx
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
接口 PostListProps {
帖子:帖子[]
}
export 函数 PostList({ 帖子 }: PostListProps) {
返回 (
<div className='grid gap-4'>
{帖子.map((帖子) => (
<Card key={帖子.id}>
<CardHeader>
<CardTitle>{帖子.title}</CardTitle>
</CardHeader>
<CardContent>{帖子.content}</CardContent>
</Card>
))}
</div>
)
}
示例 Client Component:
// app/features/posts/components/PostForm.tsx
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
export 函数 PostForm() {
常量 [标题,setTitle] = useState('')
返回 (
<form>
<Input 值={标题} onChange={(e) => setTitle(e.target.value)} />
<Button 类型='submit'>提交</Button>
</form>
)
}
📊 数据获取
主要模式:Server Components
- 直接在 Server Components 中获取数据
- 在 Server Components 中使用
async/await - 不需要
useEffect或数据获取库 - 自动请求去重
Server Actions:
- 用于突变(表单、更新)
- 创建
app/actions/目录 - 标记为
"use server"指令
示例 Server Component 与数据获取:
// app/posts/page.tsx
import { PostList } from '@/components/PostList'
异步 函数 getPosts() {
常量 res = 等待 fetch('https://api.example.com/posts', {
缓存:'no-store', // 或 'force-cache', 'revalidate'
})
返回 res.json()
}
export 默认 异步 函数 PostsPage() {
常量 帖子 = 等待 getPosts()
返回 <PostList 帖子={帖子} />
}
示例 Server Action:
// app/actions/posts.ts
'use server'
export 异步 函数 createPost(formData: FormData) {
常量 标题 = formData.get('title')
// ... 验证和创建逻辑
重定向('/posts')
}
📁 文件组织
App Router 结构:
app/
(routes)/
页面.tsx # 路由页面
布局.tsx # 路由布局
加载.tsx # 加载 UI
错误.tsx # 错误 UI
components/ # 共享组件
ui/ # Shadcn/ui 组件
特性/ # 特定功能代码
帖子/
components/ # 功能组件
actions/ # 服务器操作
类型/ # TypeScript 类型
lib/
utils.ts # 实用工具(cn 等)
hooks/
use-mobile.ts # 自定义钩子(仅限客户端)
功能组织:
app/features/{feature}/:特定功能页面/路由components/:真正可重用的组件components/ui/:Shadcn/ui 组件(不要直接修改)
🎨 样式设计
Tailwind CSS + Shadcn/ui:
- 使用 Tailwind 实用程序类
- 使用
cn()实用工具进行条件类 - Shadcn/ui 组件使用 CSS 变量进行主题设置
- 在
app/globals.css中自定义主题
样式设计模式:
import { cn } from '@/lib/utils'
接口 ButtonProps {
变体?:'primary' | 'secondary'
类名?:字符串
}
export 函数 Button({ 变体 = 'primary', 类名 }: ButtonProps) {
返回 (
<button
className={cn(
'rounded-md px-4 py-2',
变体 === 'primary' && 'bg-primary text-primary-foreground',
变体 === 'secondary' && 'bg-secondary text-secondary-foreground',
类名,
)}
>
点击我
</button>
)
}
Shadcn/ui 组件:
- 从
@/components/ui/{component-name}导入 - 组件已经样式设计和可访问性
- 通过
className属性或 CSS 变量自定义
🛣️ 路由
Next.js App Router - 文件基础:
- 目录:
app/{route-name}/page.tsx - 嵌套路由:
app/{parent}/{child}/page.tsx - 动态路由:
app/posts/[id]/page.tsx - 路由组:
app/(marketing)/about/page.tsx
示例路由:
// app/posts/page.tsx
import { Metadata } from 'next'
import { PostList } from '@/components/PostList'
export const metadata: Metadata = {
标题:'帖子',
描述:'所有帖子列表',
}
export 默认 异步 函数 PostsPage() {
常量 帖子 = 等待 getPosts()
返回 (
<div className='container mx-auto py-8'>
<h1 className='text-3xl font-bold mb-6'>帖子</h1>
<PostList 帖子={帖子} />
</div>
)
}
动态路由:
// app/posts/[id]/page.tsx
接口 PostPageProps {
params: Promise<{ id: 字符串 }>
}
export 默认 异步 函数 PostPage({ params }: PostPageProps) {
常量 { id } = 等待 params
常量 帖子 = 等待 getPost(id)
如果 (!帖子) {
未找到()
}
返回 <PostDetail 帖子={帖子} />
}
⏳ 加载 & 错误状态
加载状态:
- 在路由目录中创建
loading.tsx - 自动包装页面在 Suspense 中
- 用于路由级加载
错误边界:
- 在路由目录中创建
error.tsx - 自动捕获路由中的错误
- 可以重置错误状态
示例加载 UI:
// app/posts/loading.tsx
export 默认 函数 Loading() {
返回 (
<div className='flex items-center justify-center min-h-screen'>
<div className='animate-spin rounded-full h-8 w-8 边框-b-2 边框-主要' />
</div>
)
}
示例错误 UI:
// app/posts/error.tsx
'use client'
import { useEffect } from 'react'
import { Button } from '@/components/ui/button'
export 默认 函数 错误({
错误,
重置,
}: {
错误: 错误 & { digest?: 字符串 }
重置: () => 空
}) {
useEffect(() => {
控制台.error(错误)
}, [错误])
返回 (
<div className='flex flex-col items-center justify-center min-h-screen'>
<h2 className='text-2xl font-bold mb-4'>出了点问题!</h2>
<Button onClick={重置}>再试一次</Button>
</div>
)
}
⚡ 性能
优化模式:
- 使用 Server Components(更小的捆绑包)
- 使用
next/image用于图像 - 使用
next/font用于字体 - 尽可能延迟加载 Client Components
- 在 Client Components 中使用
useMemo和useCallback - 使用 Suspense 边界流式传输数据
图像优化:
import Image 从 'next/image'
export 函数 Avatar({ src, alt }: { src: 字符串; alt: 字符串 }) {
返回 (
<Image
src={src}
alt={alt}
宽度={40}
高度={40}
className='rounded-full'
/>
)
}
Suspense 流式传输:
import { Suspense } from 'react'
import { PostList } from '@/components/PostList'
import { Loading } from '@/components/Loading'
export 默认 函数 页面() {
返回 (
<div>
<Suspense fallback={<Loading />}>
<PostList />
</Suspense>
</div>
)
}
📘 TypeScript
标准:
- 启用严格模式
- 没有
any类型 - 函数上明确返回类型
- 类型导入:
import type { Post } from '@/types/post' - 组件 prop 接口与 JSDoc
示例:
import type { ComponentProps } from 'react'
import { Button } from '@/components/ui/button'
/**
* 自定义按钮组件,带加载状态
*/
接口 CustomButtonProps 扩展 ComponentProps<typeof Button> {
isLoading?:布尔
}
export 函数 CustomButton({
isLoading,
子代,
...props
}: CustomButtonProps) {
返回 (
<Button 禁用={isLoading} {...props}>
{isLoading ? '加载中...' : 子代}
</Button>
)
}
🔧 常见模式
表单处理:
- 使用 Server Actions 用于表单提交
- 使用
react-hook-form与zod进行验证(Client Components) - 使用 Shadcn/ui 表单组件
示例表单与服务器操作:
// app/actions/posts.ts
'use server'
import { z } from 'zod'
常量 createPostSchema = z.object({
标题:z.string().min(1),
内容:z.string().min(1),
})
export 异步 函数 createPost(formData: FormData) {
常量 rawData = {
标题:formData.get('title'),
内容:formData.get('content'),
}
常量 validated = createPostSchema.parse(rawData)
// ... 创建帖子逻辑
重定向('/posts')
}
元数据:
import { Metadata } from 'next'
export const metadata: Metadata = {
标题:'帖子',
描述:'所有帖子列表',
开放图:{
标题:'帖子',
描述:'所有帖子列表',
},
}
核心原则
- 首先 Server Components:默认使用 Server Components,仅在需要时使用 Client Components
- App Router 结构:使用文件基础路由与
app/目录 - Shadcn/ui 组件:使用预构建的可访问组件
- Tailwind CSS:实用程序优先样式设计与
cn()助手 - TypeScript 严格:没有
any,明确类型 - 性能:使用 Server Components,优化图像,需要时延迟加载
- 文件组织:功能在
app/features/中,共享在components/中 - 导入别名:使用
@/前缀进行清晰导入
快速参考:文件结构
app/
布局.tsx # 根布局
页面.tsx # 首页
全局.css # 全局样式
(routes)/
帖子/
页面.tsx # 帖子列表页面
[id]/
页面.tsx # 帖子详情页面
加载.tsx # 加载 UI
错误.tsx # 错误 UI
特性/
帖子/
components/
帖子列表.tsx # 功能组件
actions/
帖子.ts # 服务器操作
components/
ui/ # Shadcn/ui 组件
按钮.tsx
卡片.tsx
lib/
utils.ts # 实用工具(cn 等)
hooks/
use-mobile.ts # 自定义钩子
现代组件模板(快速复制)
Server Component:
// app/components/PostCard.tsx
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import 类型 { 帖子 } 从 '@/types/post'
接口 PostCardProps {
帖子:帖子
}
export 函数 PostCard({ 帖子 }: PostCardProps) {
返回 (
<Card>
<CardHeader>
<CardTitle>{帖子.title}</CardTitle>
</CardHeader>
<CardContent>
<p>{帖子.content}</p>
</CardContent>
</Card>
)
}
Client Component:
// app/components/PostForm.tsx
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { createPost } 从 '@/app/actions/posts'
import { cn } 从 '@/lib/utils'
export 函数 PostForm({ 类名 }: { 类名?:字符串 }) {
常量 [isLoading, setIsLoading] = useState(false)
异步 函数 handleSubmit(formData: FormData) {
setIsLoading(true)
等待 createPost(formData)
setIsLoading(false)
}
返回 (
<form action={handleSubmit} className={cn('space-y-4', 类名)}>
<Input 名称='title' placeholder='帖子标题' 必需 />
<Input 名称='content' placeholder='帖子内容' 必需 />
<Button 类型='submit' 禁用={isLoading}>
{isLoading ? '创建中...' : '创建帖子'}
</Button>
</form>
)
}
相关技能
- backend-dev-guidelines:前端消费的后端 API 模式
技能状态:针对 Next.js 15 与 App Router、Server Components 和 Shadcn/ui 进行优化