返回首页

使用 Zod 和 TypeScript 在运行时进行 API 验证

本文解释了 TypeScript 在运行时对 API 数据的局限性,并建议使用 Zod 进行模式验证。代码示例和库比较,适合中高级开发者。

Zod 用于 TypeScript API 的运行时验证
Advertisement 728x90

## 运行时 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 信任类型注解,忽略真实服务器响应。主要风险:

Google AdInline article slot
  • 后端结构变动:full_namefullName 导致 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() 获取包含 successerror 的对象。

Google AdInline article slot

实际项目中的扩展

大型应用中,集中管理所有端点的模式。封装 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。

Google AdInline article slot

— Editorial Team

Advertisement 728x90

继续阅读