Remix Action类型推断结合Zod验证的TS错误排查
在Remix多Action场景下结合Zod类型推断的解决方案
问题根源
单Action场景下,useActionData<typeof action>能正确推断Zod格式化错误的类型,但多Action时返回类型是多个Zod错误结构的联合类型,TypeScript无法确定当前actionData对应哪个Action的返回,导致访问_errors或动态索引时出现类型报错。
可行解决方案
1. 给Action返回加类型标识(推荐)
在每个Action的返回结果中添加一个固定的actionType字段,通过该字段缩小TypeScript的类型判断范围。
服务端Action代码:
import { z } from "zod"; import { ActionFunctionArgs } from "@remix-run/node"; // 定义不同表单的Zod校验规则 const CreateSchema = z.object({ some_string: z.string().min(1, "不能为空"), }); const UpdateSchema = z.object({ id: z.string().uuid("无效ID"), another_field: z.number().int("必须是整数"), }); export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const actionType = formData.get("actionType") as "create" | "update"; // 创建动作 if (actionType === "create") { const result = CreateSchema.safeParse(Object.fromEntries(formData)); if (!result.success) { return { actionType: "create" as const, // 固定类型,用于组件侧类型缩小 fields: Object.fromEntries(formData), errors: result.error.format(), }; } // 执行创建逻辑... } // 更新动作 if (actionType === "update") { const result = UpdateSchema.safeParse(Object.fromEntries(formData)); if (!result.success) { return { actionType: "update" as const, fields: Object.fromEntries(formData), errors: result.error.format(), }; } // 执行更新逻辑... } return { actionType: "unknown" as const }; }
组件侧代码:
import { useActionData } from "@remix-run/react"; import type { action } from "./route"; const actionData = useActionData<typeof action>(); // 根据actionType缩小类型,解决_errors访问报错 function getFieldError(input: keyof typeof CreateSchema["shape"] | keyof typeof UpdateSchema["shape"]) { if (actionData?.actionType === "create") { return actionData.errors?.[input as keyof typeof actionData.errors]?._errors?.[0]; } if (actionData?.actionType === "update") { return actionData.errors?.[input as keyof typeof actionData.errors]?._errors?.[0]; } return undefined; }
2. 手动定义联合类型并通过字段存在性缩小范围
如果不想加actionType,可以手动定义所有Action返回的类型联合,通过检查错误对象的字段存在性来缩小类型:
import { z } from "zod"; import { useActionData } from "@remix-run/react"; const CreateSchema = z.object({ some_string: z.string() }); const UpdateSchema = z.object({ id: z.string(), another_field: z.number() }); // 手动定义每个Action的错误返回类型 type CreateActionError = { fields: Record<string, FormDataEntryValue>; errors: z.ZodFormattedError<z.infer<typeof CreateSchema>, string>; }; type UpdateActionError = { fields: Record<string, FormDataEntryValue>; errors: z.ZodFormattedError<z.infer<typeof UpdateSchema>, string>; }; type ActionData = CreateActionError | UpdateActionError | undefined; const actionData = useActionData() as ActionData; // 通过检查字段存在性缩小类型 function getCreateError() { if (actionData && "some_string" in actionData.errors) { // TypeScript此时知道这是CreateActionError类型 return actionData.errors.some_string._errors[0]; } return undefined; }
3. 解决动态索引报错
如果需要用动态字符串作为索引,可通过泛型函数约束或类型断言解决:
- 泛型函数方式(更安全):
function getError<T extends keyof (typeof actionData)["errors"]>(input: T) { return actionData?.errors?.[input]?._errors?.[0]; } // 使用时传入具体字段名,自动获得类型提示 const error = getError("some_string");
- 类型断言方式(快速解决):
const input = "some_string"; const error = actionData?.errors?.[input as keyof typeof actionData.errors]?._errors?.[0];
内容的提问来源于stack exchange,提问作者sthomas
相关产品推荐
相关产品推荐

