从编译期到运行时:TypeScript 和 Zod 的职责分工
在前端工程里,TypeScript 几乎已经成了默认配置。
类型提示、自动补全、编译期报错,极大提升了开发体验。
但随着项目开始接触更多运行时不确定的数据(接口返回、配置文件、iframe、AI 输出),一个问题逐渐显现:
TypeScript 的“类型安全”,只存在于编译期。
这也是 Zod 频繁出现在近几年项目和 demo 中的原因。
一、TS 和 Zod 各自解决什么问题?
先把两者的职责说清楚。
| 维度 | TypeScript | Zod |
|---|---|---|
| 作用阶段 | 编译期 | 运行时 |
| 是否参与打包后代码 | ❌ 不存在 | ✅ 存在 |
| 是否校验真实数据 | ❌ | ✅ |
| 是否影响开发体验 | ✅ 极强 | ⚠️ 辅助 |
| 主要目标 | 防止你写错代码 | 防止数据把程序搞崩 |
一句话总结:
TS 约束“你怎么写代码”,
Zod 约束“程序实际接收到什么数据”。
两者不是替代关系,而是分工关系。
二、有了 TS,为什么还需要 Zod?
这是很多人第一次看到 Zod 时的自然疑问。
关键原因在于:
TS 是静态的,Zod 是运行时的。
1️⃣ TypeScript 只存在于编译期
type User = {
name: string;
age: number;
};
这段类型:
- 在你写代码时存在
- 在 IDE 里给你提示
- 打包之后会被完全移除
也就是说,运行时根本不存在 User 这个概念。
2️⃣ 一个非常常见的错误直觉(90% TS 新手都会这样)
type User = {
name: string;
age: number;
};
const user: User = JSON.parse(localStorage.getItem('user')!);
TS:✔️ 没问题
运行时:❓ 完全未知
为什么这是危险的?
因为 localStorage 里的内容可能是:
{ "name": "Alice", "age": "18" }
或者:
null
甚至:
"hacked"
但 TS 不会、也不可能在运行时帮你检查这些。
三、Zod 在这里解决了什么?
Zod 的核心价值只有一个:
在运行时,对真实数据做结构校验,并在失败时明确抛错。
同样的逻辑,用 Zod 写是这样的:
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
const raw = JSON.parse(localStorage.getItem('user')!);
const user = UserSchema.parse(raw);
如果数据不符合约定:
- 立即抛出错误
- 明确指出哪一项不合法
- 不会让 bug 延迟到某个业务逻辑里才爆炸
这一步,TS 是完全做不到的。
四、什么叫“不可信输入来源”?
简单说一句不抽象的定义:
任何不是你当前代码自己
new出来的数据,都是不可信的。
在实际项目中,以下全部属于不可信输入:
JSON.parse(...)-
fetch(...)/ 接口返回 postMessagelocalStorage- 第三方 SDK 返回值
- iframe / Web Worker 通信
- AI 模型返回的 JSON
这些数据的共同点是:
它们的真实形状,TS 在运行时一无所知。
五、一个更直观的“跨边界”例子
postMessage 场景
window.addEventListener('message', (event) => {
const data = event.data;
});
在这里:
-
event.data的类型是any - TS 无法保证它来自谁
- 也无法保证结构正确
如果你直接用:
data.type === 'LOGIN'
data.payload.userId
只要数据结构稍有变化,就会在运行时崩掉。
用 Zod 明确边界
const MessageSchema = z.object({
type: z.literal('LOGIN'),
payload: z.object({
userId: z.string(),
}),
});
window.addEventListener('message', (event) => {
const msg = MessageSchema.parse(event.data);
// 从这里开始,TS 才真正安全
});
这一步的意义是:
把“外部世界的数据”,转化成“TS 世界里可信的数据”。
六、Zod 一般会出现在什么项目里?
你在传统 CRUD 业务中很少看到 Zod,并不奇怪。
Zod 通常出现于这些场景:
- BFF / Node 服务
- 插件系统 / 扩展机制
- 配置驱动系统
- 微前端 / iframe 通信
- AI / Agent / Tool 调用
- MCP / function calling
它们的共同特征是:
数据频繁跨越系统、进程、运行时边界。
七、TS + Zod 的合理分工方式
一个成熟的工程里,两者的职责通常是:
-
Zod:守住边界
- 校验所有外部输入
- JSON、接口、消息、AI 输出
-
TypeScript:管理内部
- 业务逻辑
- 状态流转
- 组件和函数之间的协作
可以用一句话概括:
Zod 负责“别让脏数据进来”,
TS 负责“进来之后别把代码写错”。