TypeScript 与 Zod:谁检查代码,谁检查数据

TypeScript 和 Zod 都与“类型安全”有关,但它们工作的时间和对象完全不同。一句话概括:TypeScript 在编译阶段检查代码,Zod 在运行阶段检查真实数据。

TypeScript 和 Zod 都与“类型安全”有关,但它们工作的时间和对象完全不同。

一句话概括:

TypeScript 在编译阶段检查代码,Zod 在运行阶段检查真实数据。

TypeScript 类型会被删除

下面是一段 TypeScript:

function double(value: number): number {
  return value * 2;
}

转换成 JavaScript 后,类型信息会消失:

function double(value) {
  return value * 2;
}

这叫类型擦除。浏览器执行 JavaScript 时,并不知道 value 曾经被声明为 number

TypeScript 可以根据代码推断类型,也可以通过联合类型表达多种可能:

type Result =
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

判断 status 后,TypeScript 可以缩小变量的可能类型:

function describe(result: Result) {
  if (result.status === "success") {
    return result.data.length;
  }

  return result.message;
}

这个过程叫类型收窄。它能帮助我们安全地读取不同结构中的字段。

as 不会验证数据

开发者有时会这样处理外部数据:

const message = input as Message;

as Message 的含义只是“请让 TypeScript 相信这份数据符合 Message”。它不会检查字段,也不会转换数据。

编译后,上面的代码基本只剩:

const message = input;

如果 content 实际是数字,as Message 不会立刻报错。只有后面将它当成字符串使用时,程序才可能失败:

message.content.toUpperCase();

因此,类型断言不能代替数据验证。

Zod 在运行时验证数据

Zod Schema 是真正存在于 JavaScript 运行时的对象:

import { z } from "zod";

const MessageSchema = z.discriminatedUnion("kind", [
  z.object({
    kind: z.literal("text"),
    content: z.string()
  }),
  z.object({
    kind: z.literal("image"),
    url: z.string()
  })
]);

使用 safeParse 可以获得明确的成功或失败结果:

const result = MessageSchema.safeParse(input);

if (result.success) {
  console.log(result.data);
} else {
  console.log(result.error.issues);
}

parse 验证失败时会抛出异常,而 safeParse 会把错误放进返回结果。

Zod 还可以从 Schema 推导 TypeScript 类型:

type Message = z.infer;

这样 Schema 成为运行时数据结构的真实来源,同时 TypeScript 继续保护验证后的业务代码。

正确的数据边界

外部数据不应该一进入程序就被假定为可信数据。更稳妥的流程是:

API / 表单 / localStorage / AI 输出
-> unknown
-> Zod 验证
-> 可信的 TypeScript 数据
-> 业务逻辑

TypeScript 负责检查我们如何使用数据,Zod 负责确认收到的数据是否真的符合预期。两者不是竞争关系,而是分别守住编译期和运行时两道边界。

没有回应

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注