news 2026/9/6 6:17:47

Zod :让 TypeScript 在运行时也能检查数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zod :让 TypeScript 在运行时也能检查数据

前言

TypeScript 能够在编码和编译阶段帮助我们发现类型错误,但它无法保证程序在运行时接收到的数据一定符合类型定义。

例如,接口声明返回的是一个用户对象:

interfaceUser{name:string;age:number;}

但后端实际返回的数据可能是:

{"name":123,"age":"18"}

这份数据显然不符合User类型,但 TypeScript 不会自动检查接口真实返回的 JSON。

这正是 Zod 要解决的问题。


一、Zod 是什么?

Zod 是一个以 TypeScript 为核心的运行时数据校验库。

开发者可以通过 Zod 定义一份数据结构规则,也就是 Schema,然后使用这份 Schema:

  1. 在运行时校验数据;
  2. 获得详细的校验错误;
  3. 自动推导出对应的 TypeScript 类型。

简单来说:

TypeScript 负责检查我们编写的代码,Zod 负责检查程序运行时接收到的数据。

Zod 的基本工作流程如下:

定义 Schema ↓ 接收外部未知数据 ↓ 运行时校验 ↓ 获得类型安全的数据

二、为什么需要 Zod?

1. TypeScript 类型只存在于编译阶段

下面的代码看起来没有问题:

interfaceUser{name:string;age:number;}constuser:User={name:"小明",age:18,};

TypeScript 可以检查出开发者是否把错误类型的数据赋值给user

但是,TypeScript 类型会在编译后被删除。程序运行时并不存在User这个接口,因此 TypeScript 无法检查来自外部的数据。

常见的外部数据包括:

  • 后端接口返回值;
  • 用户提交的表单;
  • URL 查询参数;
  • JSON 配置文件;

这些数据本质上都不可信,需要在进入业务逻辑之前进行校验。


2. 类型断言不会校验数据

很多项目会使用类型断言处理接口返回值:

constresponse=awaitfetch("/api/user");constuser=(awaitresponse.json())asUser;

这里的as User并不会验证数据,它只是告诉 TypeScript:

我确定这份数据是User,请按照User类型处理。

如果接口返回:

{"name":100,"age":"十八岁"}

TypeScript 仍然会把它当成User

后续代码可能出现运行时错误:

user.name.toUpperCase();

由于真实的name是数字,调用toUpperCase()时就会报错。


3. Zod 可以建立运行时安全边界

使用 Zod 后,可以在外部数据进入业务逻辑之前完成校验:

后端接口、用户输入、配置文件 ↓ Zod 校验层 ↓ 可信的业务数据 ↓ 业务逻辑

这样做的核心价值是:

  • 错误能够更早暴露;
  • 错误位置更加明确;
  • 业务代码可以放心使用校验后的数据;
  • 减少类型定义和校验规则不一致的问题。

三、安装 Zod

使用 npm 安装:

npminstallzod

四、Zod 的基本用法

1. 定义 Schema

首先通过 Zod 描述数据应该具有什么结构:

import{z}from"zod";constUserSchema=z.object({id:z.number(),name:z.string(),age:z.number(),});

这份 Schema 表示:

  • id必须是数字;
  • name必须是字符串;
  • age必须是数字。

2. 校验数据

可以使用parse()校验数据:

constuser=UserSchema.parse({id:1001,name:"小明",age:18,});

如果数据符合 Schema,parse()会返回校验后的数据。

如果数据不符合 Schema,则会抛出异常:

UserSchema.parse({id:"1001",name:"小明",age:"18",});

这里的idage都不是数字,因此校验会失败。


3. 自动推导 TypeScript 类型

Zod 可以通过z.infer从 Schema 中自动生成 TypeScript 类型:

typeUser=z.infer<typeofUserSchema>;

推导结果相当于:

typeUser={id:number;name:string;age:number;};

这意味着我们只需要维护一份 Schema:

constUserSchema=z.object({id:z.number(),name:z.string(),age:z.number(),});typeUser=z.infer<typeofUserSchema>;

相比于分别维护类型和校验逻辑,这种方式更加可靠:

Zod Schema ├── 用于运行时校验 └── 用于推导 TypeScript 类型

五、一个完整的接口校验示例

假设有一个获取用户信息的接口:

import{z}from"zod";constUserSchema=z.object({id:z.number(),name:z.string().min(1,"姓名不能为空"),age:z.number().int().min(0,"年龄不能小于 0"),email:z.string().email("邮箱格式不正确"),});typeUser=z.infer<typeofUserSchema>;asyncfunctiongetUser():Promise<User|null>{constresponse=awaitfetch("/api/user");// 外部数据默认视为 unknown,避免在校验前直接使用constunknownData:unknown=awaitresponse.json();// 校验接口返回值,并以结果对象的方式处理失败情况constresult=UserSchema.safeParse(unknownData);if(!result.success){console.error("用户数据格式错误:",result.error);returnnull;}// 校验成功后,data 已被推导为 User 类型returnresult.data;}

如果接口返回:

{"id":1001,"name":"小明","age":18,"email":"xiaoming@example.com"}

校验成功,返回值会被推导为User类型。

如果接口返回:

{"id":"1001","name":"","age":-1,"email":"not-an-email"}

校验会失败,因为:

  • id应该是数字;
  • name不能为空;
  • age不能小于0
  • email不是合法邮箱。

六、parsesafeParse的区别

Zod 最常用的两个校验方法是:

  • parse()
  • safeParse()

1.parse()

parse()校验成功时返回数据,失败时抛出异常。

try{constuser=UserSchema.parse(input);console.log(user);}catch(error){console.error("数据校验失败:",error);}

适合以下场景:

  • 配置错误时程序不能继续运行;
  • 数据不合法属于异常情况;
  • 外层已经有统一异常处理。

2.safeParse()

safeParse()不会主动抛出异常,而是返回一个结果对象:

constresult=UserSchema.safeParse(input);if(result.success){console.log("校验成功:",result.data);}else{console.log("校验失败:",result.error);}

校验成功时:

{success:true,data:...}

校验失败时:

{success:false,error:...}

适合以下场景:

  • 表单校验;
  • 接口参数校验;
  • 需要向用户展示错误信息;
  • 校验失败后仍然需要执行其他逻辑。

选择建议

场景推荐方法
校验失败就终止执行parse()
需要手动处理校验结果safeParse()
表单提交safeParse()
应用启动配置parse()
接口返回值通常使用safeParse()

七、常见数据类型

字符串

constNameSchema=z.string();

添加长度限制:

constNameSchema=z.string().min(1,"姓名不能为空").max(20,"姓名不能超过 20 个字符");

数字

constAgeSchema=z.number();

限制为非负整数:

constAgeSchema=z.number().int("年龄必须是整数").min(0,"年龄不能小于 0");

布尔值

constEnabledSchema=z.boolean();

数组

constTagsSchema=z.array(z.string());

对应的数据:

consttags=["TypeScript","Zod","React"];

也可以限制数组长度:

constTagsSchema=z.array(z.string()).min(1,"至少选择一个标签");

对象

constProductSchema=z.object({id:z.number(),name:z.string(),price:z.number().positive(),});

可选字段

使用optional()表示字段可以不存在:

constUserSchema=z.object({name:z.string(),nickname:z.string().optional(),});

推导出的类型是:

typeUser={name:string;nickname?:string;};

允许null

使用nullable()表示字段可以是null

constUserSchema=z.object({name:z.string(),avatar:z.string().nullable(),});

这里需要区分:

z.string().optional();// string | undefinedz.string().nullable();// string | null

如果两种情况都允许:

constAvatarSchema=z.string().nullish();

可以理解为:

string|null|undefined

枚举值

constStatusSchema=z.enum(["pending","success","failed",]);typeStatus=z.infer<typeofStatusSchema>;

推导出的类型为:

typeStatus="pending"|"success"|"failed";

八、嵌套对象校验

实际业务数据通常是嵌套结构:

import{z}from"zod";constAddressSchema=z.object({province:z.string(),city:z.string(),detail:z.string(),});constUserSchema=z.object({id:z.number(),name:z.string(),address:AddressSchema,});

对应的数据:

constinput={id:1001,name:"小明",address:{province:"北京",city:"北京市",detail:"朝阳区某街道",},};constuser=UserSchema.parse(input);

复杂 Schema 可以拆分成多个小 Schema,以提高复用性和可读性。


九、自定义业务规则

基础类型正确并不代表数据一定符合业务要求。

例如,注册密码必须同时包含字母和数字:

constPasswordSchema=z.string().min(8,"密码至少需要 8 个字符").refine((password)=>/[a-zA-Z]/.test(password)&&/\d/.test(password),{message:"密码必须同时包含字母和数字",},);

或者校验确认密码是否一致:

constRegisterSchema=z.object({password:z.string().min(8),confirmPassword:z.string(),}).refine((data)=>data.password===data.confirmPassword,{message:"两次输入的密码不一致",path:["confirmPassword"],},);

这里的path表示错误应该归属到哪个字段,方便表单展示错误信息。


十、Zod 和 TypeScript 的区别

对比维度TypeScriptZod
检查时间编译阶段运行阶段
检查对象源代码真实数据
运行后是否存在不存在存在
能否校验接口返回值不能自动校验可以
能否返回校验错误不可以可以
能否推导类型可以可以通过 Schema 推导

两者并不是替代关系,而是互补关系:

TypeScript:保证代码内部的类型安全 Zod:保证外部数据进入系统时的类型安全

十一、Zod 和手写校验的区别

不使用 Zod 时,我们可能会这样校验数据:

functionisUser(value:unknown):boolean{if(typeofvalue!=="object"||value===null){returnfalse;}constuser=valueasRecord<string,unknown>;return(typeofuser.id==="number"&&typeofuser.name==="string"&&typeofuser.age==="number");}

这种方式存在一些问题:

  • 校验代码比较繁琐;
  • 嵌套数据很难维护;
  • 错误信息不够详细;
  • 类型定义和校验规则容易不一致;
  • 新增字段时可能忘记修改校验函数。

使用 Zod 后:

constUserSchema=z.object({id:z.number(),name:z.string(),age:z.number(),});

代码更加简洁,而且可以直接推导类型。


十二、常见误区

误区一:使用as就能保证数据类型

错误示例:

constuser=inputasUser;

类型断言不会校验数据,只会绕过 TypeScript 的检查。

更安全的写法:

constuser=UserSchema.parse(input);

误区二:所有内部数据都需要重复校验

Zod 最适合用在系统边界,而不是在每一个函数内部重复校验。

推荐在以下位置校验:

HTTP 请求入口 接口响应入口 表单提交入口 配置文件加载入口 本地存储读取入口 第三方数据接入入口

数据经过校验后,系统内部可以直接使用对应的 TypeScript 类型。


误区三:Zod 会自动把字符串转换成数字

下面的数据不会通过z.number()

constinput="18";z.number().parse(input);

因为"18"是字符串,不是数字。

如果需要处理表单或 URL 参数,可以明确使用类型转换规则:

constAgeSchema=z.coerce.number().int().min(0);constage=AgeSchema.parse("18");

转换后的age是数字18

需要注意:

类型转换应该是明确的业务行为,而不是默认假设所有错误类型都可以自动修复。


十三、适合使用 Zod 的场景

Zod 特别适合以下项目:

前端表单

校验用户名、手机号、邮箱、密码等用户输入。

API 接口

校验后端响应是否符合前端预期。

Node.js 服务端

校验请求参数、请求体和环境变量。

配置管理

应用启动时检查配置文件或环境变量是否合法。

全栈 TypeScript 项目

使用同一份 Schema 统一前后端的数据规则。


十四、什么时候不一定需要 Zod?

Zod 并不是所有场景都必须使用。

以下情况可以暂时不引入:

  • 项目非常小,没有外部数据输入;
  • 数据已经被其他框架可靠校验;
  • 只需要简单的编译阶段类型检查;
  • 引入额外依赖的成本大于校验收益。

是否使用 Zod,关键要看:

系统是否需要在运行时验证不可信数据。

如果项目大量依赖接口、表单、配置或者第三方数据,引入 Zod 通常很有价值。


十五、总结

Zod 的核心思想并不复杂:

不要直接相信外部数据,先校验,再使用。

它主要解决了 TypeScript 无法进行运行时数据校验的问题,并将 Schema、运行时校验和类型推导结合在一起。

使用 Zod 的基本步骤可以归纳为:

import{z}from"zod";// 1. 定义数据结构和校验规则constUserSchema=z.object({name:z.string().min(1),age:z.number().int().min(0),});// 2. 从 Schema 自动推导 TypeScript 类型typeUser=z.infer<typeofUserSchema>;// 3. 校验不可信的外部数据constresult=UserSchema.safeParse({name:"小明",age:18,});// 4. 只使用校验成功的数据if(result.success){constuser:User=result.data;console.log(user);}

最后可以用一句话记住 Zod:

TypeScript 让代码具有类型,Zod 让真实数据符合类型。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 16:33:05

OpenClaw实战:从零部署个人AI Agent与Skill开发指南

如果你最近在关注 AI Agent 方向&#xff0c;应该已经注意到一个现象&#xff1a;开源社区里突然冒出一批“个人 AI 助手”项目&#xff0c;它们不做模型训练&#xff0c;不搞复杂算法&#xff0c;却能在很短时间里把几十个大模型、十几类业务工具和一个聊天入口整合成真正能干…

作者头像 李华
网站建设 2026/9/5 11:08:04

论文AI率过高怎么办?2026年亲测20款免费降AI率工具,教你降AIGC避坑

说实话&#xff0c;现在写论文最闹心的早就不是查重率了&#xff0c;而是那个刺眼的“AIGC疑似度”。以前怕撞内容&#xff0c;现在怕被系统认定“不像活人写的”。好多同学后台吐槽&#xff1a;“明明是我一个字一个字敲出来的&#xff0c;怎么也被判AI&#xff1f;”或是“熬…

作者头像 李华
网站建设 2026/9/6 10:37:30

Python深度学习农作物病虫害识别项目:从源码拆解到实战部署

简介&#xff1a;本资源是一个基于Python与深度学习技术的农作物病虫害智能识别项目源码包&#xff0c;面向人工智能初学者、农业信息化学习者及高校课程设计学生&#xff0c;聚焦真实农业场景中的图像分类与目标检测问题。压缩包共16个文件&#xff0c;包含6个核心Python脚本&…

作者头像 李华
网站建设 2026/9/6 10:20:26

数据驱动的锂电池SOH与RUL预测:从赛题到实战

简介&#xff1a;本资源是2023年创新组竞赛赛题《基于数据驱动的动力电池健康状态评估与剩余寿命预测》的完整实现方案&#xff0c;面向计算机、人工智能、自动化、电子信息等专业的本科生、研究生及工程技术人员&#xff0c;解决动力电池SOH评估与RUL预测这一典型工业智能诊断…

作者头像 李华
网站建设 2026/9/4 15:31:24

钉钉考勤机如何结合钉钉使用:从安装到管理的完整指南

1. 引言 随着企业数字化管理的不断深入&#xff0c;考勤管理早已告别了纸质打卡和 Excel 手工统计的时代。钉钉考勤机作为钉钉生态中的硬件终端&#xff0c;能够与钉钉 App 深度打通&#xff0c;实现打卡数据实时同步、自动生成考勤报表、异常提醒等功能&#xff0c;极大提升了…

作者头像 李华
网站建设 2026/9/4 14:38:39

从酒煤电消费看板块复盘:建立次日策略的决策边界

8月13日收盘之后&#xff0c;很多人都会做同一件事&#xff1a;打开行情软件&#xff0c;看一遍酒、煤炭、电力、消费这几个板块的涨跌&#xff0c;然后在心里盘算明天该买什么、该卖什么。这个场景看起来很合理&#xff0c;但它一开始就错了。因为把四个不同逻辑的板块放在一起…

作者头像 李华