类型共识
分类:0-blog
发布于:
阅读时间:16 分钟
这是一个非常经典的问题,特别是在使用 Drizzle、Prisma 这种强类型 ORM 时。前端组件(如
react-hook-formundefinedNULL要彻底解决这种“三态(Value / Null / Undefined)”冲突,核心思路是在 API 边界层进行“归一化”处理。
1. 核心约定:谁该用什么?
为了避免逻辑混乱,建议全团队达成以下类型共识:
-
数据库(Database):只有
或Value。NULL -
后端 API(Elysia/RPC):接收端将
视为“不更新”,将undefined视为“清空”。null -
前端表单(Hook Form):初始值建议用
或"",避免使用null导致组件从受控变为非受控。undefined
2. 后端:Zod 预处理(彻底消除 Undefined)
在你的
contract.preprocess().catchment()undefinednullTypeScript
// @repo/contract export const siteConfigSchema = z.object({ key: z.string(), // 核心逻辑:如果前端没传或者是 undefined,统一转为 null 存入数据库 value: z.preprocess((val) => val === undefined ? null : val, z.string().nullable()), description: z.preprocess((val) => val === undefined ? null : val, z.string().nullable()), jsonValue: z.any().nullable(), });
3. 前端:表单初始化(彻底消除 Null)
React 里的
<input>value={null}useEffectnullundefinedTypeScript
// 在 CreateSiteConfigModal 中 useEffect(() => { if (editingConfig) { form.reset({ // 数据库回来的是 null,但表单需要 string 或 undefined key: editingConfig.key, value: editingConfig.value ?? "", description: editingConfig.description ?? "", // 👈 Nullish Coalescing 是关键 category: editingConfig.category ?? "general", jsonValue: editingConfig.jsonValue ?? {}, }); } }, [editingConfig]);
4. 提交时:工具函数过滤
在调用
mutateundefinednullTypeScript
// lib/utils.ts export function sanitizeSubmitData<T extends Record<string, any>>(data: T): T { const sanitized = { ...data }; Object.keys(sanitized).forEach((key) => { // 约定:前端的空字符串或 undefined,在入库前全部转为 null if (sanitized[key] =<mark> "" || sanitized[key] </mark>= undefined) { sanitized[key] = null as any; } }); return sanitized; } // 使用: const onSubmit = async (data: FormData) => { const cleanData = sanitizeSubmitData(data); await updateMutation.mutateAsync({ id, data: cleanData }); };
5. Drizzle 层面的防御
在你的
db/schema.ts.default(null).notNull()| nullTypeScript
export const siteConfigTable = p.pgTable("site_config", { description: p.text("description"), // 自动推导为 string | null jsonValue: p.jsonb("json_value").default({}), // 强制默认值避免 null });
总结方案:
-
查询时:
。DB (Null) -> API -> Frontend (String/Empty) -
编辑时:
。Frontend (Input value) -> API -> sanitize (Null) -> DB -
JSON 字段:对于
字段,建议数据库给一个默认值jsonb(非空),这样可以省去前端大量的{}判断。if (data.jsonValue)
这样处理后,你的 TS 错误会减少 90% 以上,因为你人为地在“进入表单”和“离开表单”这两个关口做了数据转换。
你想让我帮你把这个 工具函数集成到你现在的 sanitizeSubmitData 提交逻辑中吗?CreateSiteConfigModal
总结
-
前端表单:使用空字符串
承接""。null -
提交阶段:使用
强制将空态转为|| null。null -
契约层:保留
,它是数据库状态的真实反映。null