使用 Server Actions 的优势和注意事项是什么?

49 阅读4分钟

使用 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 包大小
  • 更好的类型安全
  • 与缓存系统集成

性能

  • 服务器端处理
  • 自动缓存管理
  • 减少客户端代码

注意事项

  • 安全性:身份验证和授权
  • 错误处理:适当的错误处理
  • 性能:避免阻塞操作
  • 数据验证:输入验证
  • 状态管理:使用数据库或缓存

最佳实践

  • 使用中间件模式
  • 结构化错误处理
  • 适当的日志记录
  • 定期安全审计