You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Next.js 14 Server Actions优雅处理多表单数据库更新

优化Next.js 14 Server Actions + useFormState的单字段表单更新代码

我在UI控制台里有「编辑标题」「编辑描述」等多个表单,每个表单只包含一个输入元素。现在用Next.js 14的Server Actions处理数据库更新,前端用useFormState做状态管理,功能已经实现,但代码重复冗余,想要更优雅的写法。

原始代码

const VideoUpdateSchemas = {
  title: z.object({
    title: z.string().min(1, { message: "Feld darf nicht leer sein." }),
  }),
  description: z.object({
    description: z.string().min(1, { message: "Feld darf nicht leer sein." }),
  }),
};

export type UpdateState = {
  message?: string;
  errorsTitle?: {
    title?: string[];
  };
  errorsDescription?: {
    description?: string[];
  };
  success: boolean;
  status?: number;
};
export async function updateVideo(prevState: UpdateState, formData: FormData) {
  const rawFormData = Object.fromEntries(formData.entries());
  const videoId = rawFormData.id as string;
  delete rawFormData.id;

  let validatedFields;
  if (typeof rawFormData.title === "string") {
    validatedFields = VideoUpdateSchemas.title.safeParse({
      title: formData.get("title"),
    });

    if (!validatedFields?.success) {
      return {
        errorsTitle: validatedFields?.error.flatten().fieldErrors,
        success: false,
      };
    }
  }
  if (typeof rawFormData.description === "string") {
    validatedFields = VideoUpdateSchemas.description.safeParse({
      description: formData.get("description"),
    });
    if (!validatedFields?.success) {
      return {
        errorsDescription: validatedFields?.error.flatten().fieldErrors,
        success: false,
      };
    }
  }

  try {
    const video = await prisma.video.update({
      where: {
        userId,
        id: videoId,
      },
      data: {
        ...rawFormData,
      },
    });
  } catch (error) {
    return {
      success: false,
    };
  }
  revalidatePath(`/dashboard/videos/${videoId}`);
  return {
    success: true,
  }
}

优化思路与实现

1. 重构Zod Schema,消除重复

抽离通用验证规则,动态生成字段Schema,避免每个字段重复编写z.object()结构。

2. 统一错误状态结构

将分散的errorsTitle、errorsDescription合并为一个errors对象,键对应字段名,减少类型定义冗余。

3. 批量处理字段验证

通过一次解析所有表单数据,替代多分支if判断,简化验证流程。

4. 自动推导类型

利用Zod的z.infer自动生成数据类型,减少手动维护类型的工作量。


优化后的完整代码

import { z } from "zod";
import { revalidatePath } from "next/cache";
import prisma from "@/lib/prisma"; // 替换为你的Prisma实例路径

// 抽离通用必填字符串验证规则
const requiredString = z.string().min(1, { message: "字段不能为空。" });

// 统一定义更新Schema,支持多字段可选更新
const VideoUpdateSchema = z.object({
  title: requiredString.optional(),
  description: requiredString.optional(),
  id: z.string(), // 视频ID为必填项
});

// 自动推导表单数据类型
type VideoUpdateData = z.infer<typeof VideoUpdateSchema>;

// 统一状态类型结构
export type UpdateState = {
  message?: string;
  errors?: Record<string, string[]>; // 键为字段名,值为错误信息数组
  success: boolean;
  status?: number;
};

export async function updateVideo(prevState: UpdateState, formData: FormData) {
  try {
    // 解析并验证所有表单数据
    const parsedResult = VideoUpdateSchema.safeParse(Object.fromEntries(formData.entries()));
    
    if (!parsedResult.success) {
      // 返回扁平化的错误结构
      return {
        errors: parsedResult.error.flatten().fieldErrors,
        success: false,
      };
    }

    const { id: videoId, ...updateData } = parsedResult.data;

    // 防止空提交:确保至少有一个字段需要更新
    if (Object.keys(updateData).length === 0) {
      return {
        message: "请输入要更新的内容",
        success: false,
      };
    }

    // 执行数据库更新
    await prisma.video.update({
      where: {
        userId, // 假设userId已通过鉴权逻辑获取
        id: videoId,
      },
      data: updateData,
    });

    // 重新验证页面路径,更新缓存
    revalidatePath(`/dashboard/videos/${videoId}`);
    return { success: true };
  } catch (error) {
    return {
      message: "更新失败,请稍后重试",
      success: false,
    };
  }
}

优化点说明

  • Schema复用:新增字段时只需在VideoUpdateSchema中添加,无需重复编写验证逻辑。
  • 类型自动维护:通过Zod推导数据类型,避免手动更新UpdateState中的错误字段定义。
  • 简化前端逻辑:前端可通过errors?.title、errors?.description直接获取对应字段错误,状态判断更简洁。
  • 增加边界处理:新增空提交判断,避免无效数据库请求,提升用户体验。

内容的提问来源于stack exchange,提问作者WorkoutBuddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:33:15