使用 Server Actions 的优势和注意事项是什么?
Server Actions 的优势
1. 渐进式增强
Server Actions 支持渐进式增强,即使 JavaScript 被禁用,表单仍然可以正常工作:
// 基础表单 - 无需 JavaScript
export default function BasicForm() {
return (
<form action={createUser}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Create User</button>
</form>
)
}
// 增强表单 - 添加 JavaScript 功能
'use client'
import { createUser } from '@/app/actions/user-actions'
import { useState } from 'react'
export default function EnhancedForm() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [message, setMessage] = useState('')
const handleSubmit = async (formData) => {
setIsSubmitting(true)
setMessage('')
try {
const result = await createUser(formData)
setMessage('User created successfully!')
} catch (error) {
setMessage('Error: ' + error.message)
} finally {
setIsSubmitting(false)
}
}
return (
<form action={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Creating...' : 'Create User'}
</button>
{message && <p>{message}</p>}
</form>
)
}
2. 减少 JavaScript 包大小
Server Actions 减少了客户端 JavaScript 的需求:
// 传统方式:需要大量客户端代码
'use client'
import { useState } from 'react'
export default function TraditionalForm() {
const [formData, setFormData] = useState({ name: '', email: '' })
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value })
}
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
setError(null)
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
if (!response.ok) {
throw new Error('Failed to create user')
}
const result = await response.json()
// 处理成功响应
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
required
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create User'}
</button>
{error && <p>{error}</p>}
</form>
)
}
// Server Actions 方式:更简洁
export default function ServerActionForm() {
return (
<form action={createUser}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Create User</button>
</form>
)
}
3. 与缓存集成
Server Actions 与 Next.js 缓存系统无缝集成:
// app/actions/cache-integration-actions.js
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
import { db } from '@/lib/database'
export async function createPost(formData) {
const title = formData.get('title')
const content = formData.get('content')
const post = await db.posts.create({
data: { title, content, published: true },
})
// 自动重新验证相关缓存
revalidatePath('/blog')
revalidatePath('/blog/[slug]', 'page')
revalidateTag('posts')
return { success: true, post }
}
export async function updatePost(postId, formData) {
const title = formData.get('title')
const content = formData.get('content')
const post = await db.posts.update({
where: { id: parseInt(postId) },
data: { title, content },
})
// 精确的缓存失效
revalidatePath(`/blog/${postId}`)
revalidateTag(`post-${postId}`)
return { success: true, post }
}
4. 类型安全
Server Actions 提供更好的类型安全:
// app/actions/typed-actions.js
'use server'
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(18),
})
export async function createTypedUser(formData: FormData) {
try {
const data = {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age') || '0'),
}
const validatedData = userSchema.parse(data)
const user = await db.users.create({
data: validatedData,
})
return { success: true, user }
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
return { success: false, error: error.message }
}
}
注意事项
1. 安全性考虑
// 错误示例:直接暴露敏感操作
'use server'
export async function dangerousAction(formData) {
const userId = formData.get('userId')
// ❌ 危险:没有权限检查
await db.users.delete({
where: { id: parseInt(userId) },
})
return { success: true }
}
// 正确示例:添加身份验证和授权
;('use server')
import { requireAuth } from '@/lib/auth'
export async function safeAction(formData) {
// ✅ 安全:验证用户身份
const user = await requireAuth()
if (user.role !== 'admin') {
throw new Error('Insufficient permissions')
}
const userId = formData.get('userId')
// 验证用户存在
const targetUser = await db.users.findUnique({
where: { id: parseInt(userId) },
})
if (!targetUser) {
throw new Error('User not found')
}
await db.users.delete({
where: { id: parseInt(userId) },
})
return { success: true }
}
2. 错误处理
// 错误示例:不处理错误
'use server'
export async function badErrorHandling(formData) {
const name = formData.get('name')
// ❌ 错误:没有错误处理
const user = await db.users.create({
data: { name },
})
return { success: true, user }
}
// 正确示例:适当的错误处理
;('use server')
export async function goodErrorHandling(formData) {
try {
const name = formData.get('name')
if (!name) {
throw new Error('Name is required')
}
const user = await db.users.create({
data: { name },
})
return { success: true, user }
} catch (error) {
console.error('Error creating user:', error)
if (error.code === 'P2002') {
return { success: false, error: 'User already exists' }
}
return { success: false, error: 'Failed to create user' }
}
}
3. 性能考虑
// 错误示例:阻塞操作
'use server'
export async function blockingAction(formData) {
const data = formData.get('data')
// ❌ 错误:阻塞操作
const result = await heavyComputation(data)
return { success: true, result }
}
// 正确示例:异步处理
;('use server')
export async function nonBlockingAction(formData) {
const data = formData.get('data')
// ✅ 正确:异步处理
const jobId = await queueJob(data)
return { success: true, jobId }
}
// 后台处理
export async function processJob(jobId) {
const job = await getJob(jobId)
const result = await heavyComputation(job.data)
await updateJobStatus(jobId, 'completed', result)
}
4. 数据验证
// 错误示例:没有数据验证
'use server'
export async function noValidation(formData) {
const email = formData.get('email')
// ❌ 错误:没有验证
const user = await db.users.create({
data: { email },
})
return { success: true, user }
}
// 正确示例:完整的数据验证
;('use server')
import { z } from 'zod'
const emailSchema = z.object({
email: z.string().email('Invalid email format'),
})
export async function withValidation(formData) {
try {
const data = {
email: formData.get('email'),
}
// ✅ 正确:验证数据
const validatedData = emailSchema.parse(data)
// 检查重复
const existingUser = await db.users.findUnique({
where: { email: validatedData.email },
})
if (existingUser) {
throw new Error('Email already exists')
}
const user = await db.users.create({
data: validatedData,
})
return { success: true, user }
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
return { success: false, error: error.message }
}
}
5. 状态管理
// 错误示例:在 Server Action 中管理状态
'use server'
let globalState = {} // ❌ 错误:全局状态
export async function badStateManagement(formData) {
const data = formData.get('data')
// ❌ 错误:修改全局状态
globalState[Date.now()] = data
return { success: true }
}
// 正确示例:使用数据库或缓存
;('use server')
export async function goodStateManagement(formData) {
const data = formData.get('data')
// ✅ 正确:使用数据库
const record = await db.temporaryData.create({
data: { content: data, expiresAt: new Date(Date.now() + 3600000) },
})
return { success: true, id: record.id }
}
最佳实践
1. 结构化错误处理
// lib/action-utils.js
export function createActionHandler(action) {
return async (formData) => {
try {
const result = await action(formData)
return { success: true, data: result }
} catch (error) {
console.error('Action error:', error)
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
return { success: false, error: error.message }
}
}
}
// app/actions/structured-actions.js
'use server'
import { createActionHandler } from '@/lib/action-utils'
async function createUserAction(formData) {
const name = formData.get('name')
const email = formData.get('email')
if (!name || !email) {
throw new Error('Name and email are required')
}
const user = await db.users.create({
data: { name, email },
})
revalidatePath('/users')
return user
}
export const createUser = createActionHandler(createUserAction)
2. 中间件模式
// lib/action-middleware.js
export function withAuth(action) {
return async (formData) => {
const user = await requireAuth()
return action(formData, user)
}
}
export function withValidation(schema, action) {
return async (formData) => {
const data = Object.fromEntries(formData.entries())
const validatedData = schema.parse(data)
return action(validatedData)
}
}
// app/actions/middleware-actions.js
'use server'
import { withAuth, withValidation } from '@/lib/action-middleware'
import { userSchema } from '@/lib/schemas'
async function createUserAction(data) {
const user = await db.users.create({ data })
revalidatePath('/users')
return user
}
export const createUser = withValidation(userSchema, createUserAction)
export const createAdminUser = withAuth(createUserAction)
总结
Server Actions 的优势:
开发体验:
- 渐进式增强
- 减少 JavaScript 包大小
- 更好的类型安全
- 与缓存系统集成
性能:
- 服务器端处理
- 自动缓存管理
- 减少客户端代码
注意事项:
- 安全性:身份验证和授权
- 错误处理:适当的错误处理
- 性能:避免阻塞操作
- 数据验证:输入验证
- 状态管理:使用数据库或缓存
最佳实践:
- 使用中间件模式
- 结构化错误处理
- 适当的日志记录
- 定期安全审计