Chapter 01

TS 到不了的地方

TypeScript 的类型在 tsc 完成那一刻就消失了——运行时只有 JavaScript。API 返回 JSON、表单提交字符串、localStorage 吐出来的谁知道是啥——这时候你需要一个在运行时也能执行的类型守门员。

先看一个真实 bug

interface User {
  id: string;
  name: string;
  age: number;
}

async function loadUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  const user = await res.json() as User;   // ← 谎言
  return user;
}

const u = await loadUser("1");
console.log(u.age.toFixed(0));   // TypeError: u.age.toFixed is not a function
// 服务端返回 { id, name, age: "25" }(字符串!),TS 根本不知道

TS 的 as User断言,不是校验——它只是让编译器闭嘴,并不检查数据真长这样。

TS 管不到的三个场景

HTTP / API 返回
后端改了字段、字段类型变了、nullable 没标——前端 TS 完全蒙在鼓里。
表单 / 用户输入
HTML input 永远返回 string,age: 18 实际是 "18"。还有恶意输入 / 空值 / 超长。
localStorage / cookie / URL 参数
JSON.parse(localStorage.getItem("user")!) 返回 any——schema 可能是三个月前版本存进去的。
第三方 SDK / LLM 输出
GPT 的 JSON 模式也会偶尔返回 "score": "0.9" 而不是 number。

手写校验的痛

function isUser(x: unknown): x is User {
  if (typeof x !== "object" || x === null) return false;
  const o = x as any;
  return typeof o.id === "string"
    && typeof o.name === "string"
    && typeof o.age === "number";
}

问题:

Zod 的核心想法

import { z } from "zod";

const User = z.object({
  id: z.string(),
  name: z.string().min(1),
  age: z.number().int().min(0),
});

type User = z.infer<typeof User>;
// { id: string; name: string; age: number }

const u = User.parse(await res.json());
// u 类型是 User,且数据真的是 User——否则 parse 抛异常

一份 schema,三个收益:

  1. 运行时校验——不对就抛错
  2. TS 类型自动推断——不用重复写 interface
  3. 错误信息——哪个字段错、为什么错,结构化返回

校验库对比

ZodYupJoiio-tsValibot
TS 优先后加的后加的
类型推断 z.infer部分✓(啰嗦)
错误信息结构化字符串结构化函数式结构化
bundle 大小~12KB(v4)~30KB~150KB~15KB~2KB
生态极广较广Node 为主学术派
学习曲线高(需 fp-ts)
为什么 Zod 赢了
Yup 比它老,但 TS 类型推断是后期打补丁;io-ts 类型最严格,但 fp-ts 函数式 API 大部分前端人接受不了;Joi 是 Node 时代产物,bundle 大。Zod 拿捏了"TS 友好 + API 像 Yup 简单 + 类型精准"三角平衡。

Valibot 的崛起

Valibot 是 2024 起的"挑战者"——核心思想类似 Zod,但用函数式组合:

// Zod
z.string().email().min(5);

// Valibot
pipe(string(), email(), minLength(5));

Valibot 的 bundle 小(tree-shakable 到 ~2KB),但 Zod v4 bundle 大幅压缩后差距没那么大——生态还是 Zod 压倒性。

Zod v4 为什么重要

2024 年 Colin McDonnell(Zod 作者)宣布 v4 从零重写——2025 发布,2026 稳定:

v3 vs v4
本教程以 Zod v4 为主(2026 生态主流)。大多数 API 和 v3 兼容,迁移只需更新少量类型签名——第 9 章详细讲迁移。

Zod 不适合的场景

生态地位

学会 Zod 基本就拿到了 TS 生态的通票。

一个真实例子:环境变量校验

// env.ts
import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  PORT: z.coerce.number().int().min(1).max(65535),
  NODE_ENV: z.enum(["development", "production", "test"]),
  STRIPE_SECRET: z.string().startsWith("sk_"),
});

export const env = envSchema.parse(process.env);
// 启动即验证:NODE_ENV=foobar 直接崩,带行号错误信息
// 比运行到第 20 秒才发现 config 错了友好多了

这就是 Zod 的"防御式编程"风格——在系统边界(HTTP / env / 文件 / DB 输出)设校验卡点,内部代码可以放心假设类型正确。

本章小结