## 运行时 API 响应验证:TypeScript 为何不够用
TypeScript 只在编译时检查类型。API 返回的数据在运行时到来,无法保证与你的接口定义匹配。没有额外验证,数据不匹配会导致用户直面错误:字段 undefined、类型错误、对象缺失。
缺少运行时检查的问题
TypeScript 接口给人虚假的安全感。以下是典型无验证代码:
interface User {
id: number;
name: string;
email: string;
}
const getUser = async (): Promise<User> => {
const res = await fetch('/api/user');
return res.json(); // 未检查实际数据
};
TypeScript 信任类型注解,忽略真实服务器响应。主要风险:
- 后端结构变动:
full_name→fullName导致 undefined。 - 类型不匹配:字符串
"123"而非数字123,导致拼接而非加法。 - 条件字段:边缘情况下
address缺失,访问user.address.city崩溃。 - 第三方 API 文档过时。
模式验证库
解决方案?运行时模式验证库。它们解析真实数据,不匹配时抛出错误。TypeScript 首选库:
- Zod:从模式推断类型,生态庞大。
- Yup:与 Formik 完美搭配。
- Valibot:体积小,支持 tree-shake。
- Joi:Node.js 出身,后端友好。
- ArkType:性能优先。
使用 Zod 实现
Zod 将模式、类型和验证融为一体。用户示例:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
const getUser = async (): Promise<User> => {
const res = await fetch('/api/user');
const data = await res.json();
return UserSchema.parse(data); // 不匹配抛错
};
parse() 在失败时抛出详细的 ZodError。用 safeParse() 获取包含 success 和 error 的对象。
实际项目中的扩展
大型应用中,集中管理所有端点的模式。封装 fetch:
const apiFetch = async <T>(url: string, schema: z.ZodSchema<T>): Promise<T> => {
const res = await fetch(url);
const data = await res.json();
return schema.parse(data);
};
const users = await apiFetch('/api/users', z.array(UserSchema));
错误处理:记录到 Sentry,向用户显示友好提示。对于数组,逐项验证。
- 列表用
z.array(UserSchema)。 - 多态响应用
z.union()。 - 递归结构用
z.lazy()。 - 数据标准化用
z.transform()。
核心要点
- TypeScript 无法保证外部数据运行时类型:API、localStorage、URL 参数。
- 模式验证及早捕获不匹配,减少调试时间。
- Zod 是前端标准:模式生类型,集成简单。
- 后端独立团队中,防止静默失败。
- 用 fetch 封装实现一致性扩展。
运行时验证填补了 TypeScript 编译时检查与执行间的空白。在生产环境中,大幅减少 API 契约不同步导致的 bug。
— Editorial Team
暂无评论。