ゴゴゴゴゴ
Skip to content
JOJO
首页文章项目知识库相册音乐
全站搜索
  • Navigate
  • Go to Page
  • escClose
© 2026 JOJOBuilt with Next.js

四种声音一个时代

这里收藏周杰伦、王力宏、陶喆与林俊杰的专辑,也把四位创作者放回完整唱片的语境。陶喆以松弛的律动重塑华语 R&B,周杰伦把说唱、古典与中国式旋律带进流行音乐,王力宏在抒情、摇滚和东方元素之间拓宽制作边界,林俊杰则用细密旋律与宽阔声线记录城市情绪。这里不打乱曲序,也不把专辑压缩成热门单曲;挑一张封面,从第一首听到最后一首,感受开场、中段与尾声如何共同完成一段叙事。

左右滑动浏览,轻触封面,从第一首开始听

正在播放的唱片

选择一首歌曲查看歌词
专辑封面

专辑

    请选择一张唱片等待选曲
    0:00
    0:00

    键盘快捷键

    搜索
    / 或 ⌘K
    回到首页
    G H
    博客列表
    G B
    切换主题
    T
    关闭弹窗
    Esc
    显示帮助
    ?

    按 Esc 关闭此窗口

    Blog550
    • 全部文章(550)
    • 包管理工具(8)
      • Bun 完全指南
      • fnm 完全指南
      • ni 完全指南
      • npm 完全指南
      • nrm 完全指南
      • nvm 完全指南
      • pnpm 完全指南
      • Yarn 完全指南
    • 并发异步(7)
      • 异步任务取消机制
      • Promise静态方法汇总
      • 封装超时工具方法
      • 并发与并行
      • 封装并发任务方法
      • 一道并发相关的面试题
      • 文件写入的原子化与并发操作
    • 打包工具(10)
      • Bun - 全栈 JavaScript 运行时与打包器
      • esbuild - 用 Go 写的极速打包器
      • Parcel - 零配置的极速打包工具
      • Rolldown - Vite 团队打造的 Rust 打包器
      • Rollup - 专注于库开发的打包工具
      • Rspack - Rust 驱动的 Webpack 替代品
      • SWC - Rust 驱动的超快编译器
      • Turbopack - Rust 驱动的下一代打包工具
      • Vite - 下一代前端构建工具
      • Webpack - 功能最强大的模块打包器
    • 算法(74)
      • 课程必看
      • 算法基本概念
      • 评判算法的好坏
      • 复习对数
      • 常见的复杂度
      • 常见算法思维
      • 递归
      • 数据结构
      • 数组结构
      • 顺序查找
      • 二分查找
      • 插值查找
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 折半插入排序
      • 希尔排序
      • 归并排序
      • 快速排序
      • 链表结构
      • 双向链表
      • 单向循环链表
      • 双向循环链表
      • 栈结构
      • 队列结构
      • 循环队列
      • 击鼓传花游戏
      • 双端队列
      • 回文检查器
      • 集合结构
      • 字典结构
      • 树结构
      • 二叉树
      • 二叉树遍历
      • 前序中序还原二叉树
      • 后序中序还原二叉树
      • 二叉树深度优先搜索
      • 二叉树广度优先搜索
      • 二叉树的比较
      • 二叉树diff算法
      • 二叉搜索树
      • 二叉搜索树常见操作
      • 平衡二叉树
      • 二叉树左单旋
      • 二叉树右单旋
      • 二叉树双旋
      • 二叉堆
      • 堆排序
      • 哈夫曼树
      • 图结构
      • 邻接矩阵
      • 邻接表
      • 构建图
      • 图的深度优先搜索
      • 图的广度优先搜索
      • 普里姆算法
      • 克鲁斯卡尔算法
      • LRU算法
      • 动态规划
      • 青蛙跳台阶
      • 不同路径问题
      • 凑零钱问题
      • 打家劫舍
      • 背包问题
      • 回溯算法
      • 数字组合
      • 电话号码字母组合
      • 复原IP地址
      • 数组子集
      • 贪心算法
      • 柠檬水找零
      • 摆动序列
      • 分发糖果
      • 跳跃游戏
    • 文档协同(12)
      • 课程说明
      • 文档协同常见方案
      • OT冲突算法
      • CRDT冲突算法
      • CRDT数据结构
      • 文章翻译
      • Yjs基础介绍
      • Yjs快速上手
      • Yjs细节补充
      • 监听机制
      • 标签页同步
      • WebSocket基础知识
    • 项目实战(1)
      • ShipAny:1小时开发海外游客「中国攻略 + AI旅游助手」SaaS网站
    • AI工具(9)
      • Claude Code 完全指南
      • ComfyUI 完全指南
      • GitHub Copilot 入门指南
      • Coze 完全指南
      • Cursor vs GitHub Copilot 全面对比
      • Cursor-完整指南
      • Dify 完全指南
      • Droid 完全指南
      • n8n 完全指南
    • Astro(5)
      • Astro 入门指南
      • Astro 路由与页面
      • Astro 组件与样式
      • Astro 内容集合
      • Astro 集成与部署
    • CSS(25)
      • CSS 选择器基础
      • CSS 盒模型
      • CSS 定位与层叠
      • 浮动与 BFC
      • Flexbox 布局
      • Grid 布局
      • 响应式设计
      • 伪类与伪元素
      • 现代选择器
      • CSS 变量
      • 层叠层
      • 过渡效果
      • CSS 动画
      • 变换与滤镜
      • 容器查询
      • 文字与排版
      • 背景与渐变
      • 滚动与溢出
      • 裁剪与遮罩
      • 颜色系统
      • 边框与阴影
      • 逻辑属性
      • CSS 函数
      • 多列布局
      • 用户交互
    • ES6+(40)
      • let 和 const 命令
      • 解构赋值基础
      • 解构赋值进阶
      • 字符串的扩展
      • 字符串新增方法
      • 正则表达式扩展
      • 数值的扩展
      • BigInt 大整数
      • 函数默认参数
      • rest 参数与扩展运算符
      • 箭头函数详解
      • 尾调用优化
      • 数组扩展方法
      • 数组新增方法
      • 数组不可变方法
      • 对象的扩展
      • Object 新增方法
      • Symbol 基础
      • Symbol 进阶应用
      • Set 数据结构
      • Map 数据结构
      • TypedArray 类型化数组
      • Proxy 基础
      • Proxy 进阶应用
      • Reflect 元编程
      • 元编程实战案例
      • 装饰器 Decorator
      • Iterator 迭代器
      • Generator 基础
      • Generator 进阶
      • Promise 基础
      • Promise 进阶
      • async 函数
      • await 与错误处理
      • 异步迭代
      • Class 基础
      • Class 继承
      • Module 语法
      • Module 加载
      • ES 新特性汇总
    • HTML(8)
      • HTML 入门:构建 Web 的基石
      • HTML5 语义化:让页面更有意义
      • HTML 表单基础:用户交互的起点
      • HTML 表单进阶:验证与现代特性
      • HTML 多媒体:音视频与响应式图片
      • HTML APIs(上):客户端存储完全指南
      • HTML APIs(下):交互与设备能力
      • Canvas 与 SVG:Web 图形绘制入门
    • JavaScript(30)
      • JavaScript 简介
      • 变量与数据类型
      • 运算符
      • 类型转换
      • 控制流语句
      • 函数基础
      • 对象基础
      • 数组基础
      • 原型与原型链
      • this 关键字
      • 面向对象编程
      • 作用域
      • 闭包
      • 执行上下文
      • DOM 选择器
      • DOM 操作
      • 事件处理
      • 事件委托
      • 错误处理
      • 调试技巧
      • 正则表达式
      • Date 日期对象
      • Math 对象
      • 定时器
      • JSON 处理
      • BOM 基础
      • 本地存储
      • 表单处理
      • AJAX 与 Fetch
      • 严格模式
    • Langchain(37)
      • Langchain基本介绍
      • 模型接口
      • 缓存
      • 提示词模板
      • 结构化提示词
      • Message消息对象
      • 消息占位
      • LCEL
      • 并发流式返回结果
      • Runnable接口
      • 条件路由
      • 字符串解析器
      • 结构化输出解析器
      • 列表输出解析器
      • JSON解析器
      • 正则解析器
      • 本地资源Loader
      • Web资源Loader
      • 文本切割
      • 嵌入处理
      • 内存型向量库
      • 检索优化
      • RAG实践
      • Memory基础
      • 自动维护聊天记录
      • 实现长程记忆
      • 持久化记忆
      • 第三方存储
      • Tools基础
      • Tools配置
      • Zod基础
      • LangChain中使用工具
      • 重新认识Agent
      • LangChain中创建Agent
      • Agent实践
      • 回退机制
      • 回调机制
    • LLM(34)
      • LLM发展历史
      • N-Gram模型
      • 词袋模型
      • 词元
      • 嵌入
      • Transformer内部结构
      • Transformer解码策略
      • Transformer块
      • 本地部署大模型
      • Agent
      • 流式返回信息
      • 支持上下文
      • 获取实时消息
      • 外接DeepSeek
      • Function Calling
      • MCP理论知识
      • Resources
      • Schema
      • 注册资源模板
      • Prompts
      • 监听资源更新
      • 远程通信方式
      • MCP Client
      • 大模型幻觉
      • RCT构词法
      • 提示词常见技巧
      • 基于反馈迭代优化
      • 改善提示效果
      • 提示词评估工具
      • RAG基础知识
      • RAG实践
      • RAG架构演进
      • 大模型微调
      • LoRA微调
    • MCP(8)
      • MCP 入门:连接 AI 与外部世界的桥梁
      • 搭建第一个 MCP Server
      • MCP Tools 深入:让 AI 具备行动能力
      • MCP Resources:向 AI 暴露数据
      • MCP Prompts:预定义 AI 交互模板
      • MCP Transport 与部署
      • 构建 MCP Client:集成到你的应用
      • 实战:构建文档助手 MCP Server
    • Nest(31)
      • 快速上手Nest Cli
      • 三层架构与MVC
      • IoC
      • Nest中的装饰器
      • 模块的基本概念
      • 什么是AOP
      • 中间件与守卫
      • 拦截器
      • 文件上传
      • 自定义装饰器
      • mysql相关回顾
      • 对于ORM的理解
      • 快速上手Redis
      • Nest中使用Redis
      • 状态保存方式
      • Nest中实现Session与JWT
      • JWT登录注册后端处理
      • 使用Passport与循环引用问题
      • 通过环境变量获取配置信息
      • RBAC权限设计1
      • RBAC权限设计2
      • Nest中使用Websocket
      • 日志处理
      • 面向切面的日志处理
      • 快速入门GraphQL
      • Nest中使用GraphQL
      • Swagger API文档
      • 单元测试与E2E测试
      • 安全防护实践
      • 定时任务与任务调度
      • Bull队列与异步任务
    • Next(36)
      • Next.js 简介
      • 项目创建与配置
      • App Router 概述
      • 创建第一个页面
      • 开发工具与调试
      • TypeScript 配置
      • 文件系统路由
      • 动态路由
      • 嵌套路由与布局
      • 路由组
      • 并行路由
      • 拦截路由
      • 导航与链接
      • 中间件
      • Server Components
      • Client Components
      • 混合渲染模式
      • Streaming 与 Suspense
      • 静态与动态渲染
      • 数据获取基础
      • 缓存策略
      • Server Actions
      • Route Handlers
      • 数据库集成
      • 错误处理
      • 图片优化
      • 字体优化
      • 脚本优化
      • 元数据与 SEO
      • 性能分析
      • 认证基础
      • NextAuth 集成
      • 环境变量与配置
      • 测试策略
      • 部署方案
      • 项目实战
    • Nuxt(36)
      • web应用变迁史
      • 课程说明
      • SSR初体验
      • 水合操作
      • 改造CSR项目
      • 基本介绍
      • 配置概览
      • 组件类型
      • Nuxt模块
      • 嵌套路由
      • 动态路由
      • 请求数据
      • server目录
      • 应用部署
      • 中间件
      • 插件
      • layers
      • 过渡
      • 状态管理
      • 全匹配路由
      • 路由补充知识
      • 自定义路由
      • 自定义模块
      • 自定义指令
      • meta相关处理
      • 渲染模式
      • 错误处理
      • 不同阶段错误
      • 客户端渲染组件
      • 其余内置组件
      • 配置相关细节
      • 常用组合式函数
      • 服务器引擎
      • NuxtUI
      • SSR注意事项
      • 课程收官
    • React(55)
      • 序章:React课程介绍
      • React 基本介绍
      • JSX 基础语法
      • 组件与事件绑定
      • 组件状态与数据传递
      • 表单
      • 生命周期
      • Hooks
      • React-router介绍
      • React-router v版本学生管理系统课堂笔记
      • React-redux 介绍
      • Antd介绍
      • coder station前台系统笔记
      • coder station 后台系统笔记
      • Antd Pro介绍
      • Dva介绍
      • Umi.js 介绍
      • 属性默认值和类型验证
      • 高阶组件
      • Ref
      • Context
      • Render Props
      • Portals
      • 错误边界
      • 组件渲染性能优化
      • 前端框架的理解
      • Scheduler调度延时任务
      • 最小堆
      • React中的位运算
      • beginWork工作流程
      • completeWork工作流程
      • 图解diff算法
      • commit 工作流程
      • lane模型
      • React 中的事件
      • Hooks原理
      • React和Vue描述页面的区别
      • useState和useReducer
      • effect相关hook
      • useCallback和useMemo
      • useRef
      • Update
      • 性能优化策略之eagerState
      • 性能优化策略之bailout
      • bailout和ContextAPI
      • 性能优化对日常开发启示
      • 前端框架的分类
      • 虚拟 DOM
      • React 整体架构
      • React 渲染流程
      • Fiber双缓冲
      • MessageChannel
      • Scheduler调度普通任务
      • 课程介绍
      • 课程总结
    • ReactNative(33)
      • RN 基本介绍
      • 快速搭建 RN 开发环境
      • 复习 React 基础知识
      • RN 基础知识
      • 实战案例
      • RN 内置基础组件
      • 容器组件
      • Pressable 组件
      • 列表组件
      • 功能组件
      • 第三方组件库
      • 自定义组件案例:弹框组件
      • 自定义组件案例:单选组件
      • 基础API
      • 屏幕API
      • 设备API
      • 动画API
      • 手势API
      • React Navigation简介
      • 参数传递和标题栏信息配置
      • 嵌套路由与生命周期
      • 其他类型的导航
      • 状态管理介绍
      • Redux
      • 其他第三方库
      • Expo SDK
      • Fabric 架构
      • 渲染、提交和挂载
      • 实现跨平台
      • 扁平化视图
      • 线程模型
      • 如何打包发布应用
      • 通过 React Native CLI 搭建环境
    • TailwindCSS(7)
      • TailwindCSS基础介绍
      • 环境搭建与配置
      • 布局系统
      • 间距与尺寸系统
      • 颜色与主题
      • 响应式设计
      • 交互与动画
    • TypeScript(35)
      • 初识 TypeScript
      • 基础类型
      • 数组与元组
      • 对象类型
      • 函数类型
      • 类型推断与断言
      • 联合类型与交叉类型
      • 字面量类型与类型别名
      • 接口基础
      • 接口进阶
      • 接口继承与实现
      • 类基础
      • 类的访问修饰符
      • 类的继承
      • 类与接口对比
      • 泛型基础
      • 泛型接口与泛型类
      • 泛型约束
      • 内置工具类型(上)
      • 内置工具类型(下)
      • 泛型实战
      • 类型守卫
      • 类型收窄
      • 索引类型与索引签名
      • 映射类型
      • 条件类型
      • infer 关键字
      • 模板字面量类型
      • 模块系统
      • 命名空间
      • 声明文件基础
      • 声明文件进阶
      • tsconfig 配置详解
      • 类型体操入门
      • TypeScript 最佳实践
    • 杂谈(9)
      • Cloudflare 全栈部署指南
      • Creem 支付集成指南
      • Dokploy 自托管部署指南
      • GitHub OAuth 登录集成指南
      • Google OAuth 登录集成指南
      • Stripe 支付集成指南
      • Supabase 完全指南
      • Vercel 自动化部署指南
      • Zeabur 部署指南
    • 全部文章(550)
    • 包管理工具(8)
      • Bun 完全指南
      • fnm 完全指南
      • ni 完全指南
      • npm 完全指南
      • nrm 完全指南
      • nvm 完全指南
      • pnpm 完全指南
      • Yarn 完全指南
    • 并发异步(7)
      • 异步任务取消机制
      • Promise静态方法汇总
      • 封装超时工具方法
      • 并发与并行
      • 封装并发任务方法
      • 一道并发相关的面试题
      • 文件写入的原子化与并发操作
    • 打包工具(10)
      • Bun - 全栈 JavaScript 运行时与打包器
      • esbuild - 用 Go 写的极速打包器
      • Parcel - 零配置的极速打包工具
      • Rolldown - Vite 团队打造的 Rust 打包器
      • Rollup - 专注于库开发的打包工具
      • Rspack - Rust 驱动的 Webpack 替代品
      • SWC - Rust 驱动的超快编译器
      • Turbopack - Rust 驱动的下一代打包工具
      • Vite - 下一代前端构建工具
      • Webpack - 功能最强大的模块打包器
    • 算法(74)
      • 课程必看
      • 算法基本概念
      • 评判算法的好坏
      • 复习对数
      • 常见的复杂度
      • 常见算法思维
      • 递归
      • 数据结构
      • 数组结构
      • 顺序查找
      • 二分查找
      • 插值查找
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 折半插入排序
      • 希尔排序
      • 归并排序
      • 快速排序
      • 链表结构
      • 双向链表
      • 单向循环链表
      • 双向循环链表
      • 栈结构
      • 队列结构
      • 循环队列
      • 击鼓传花游戏
      • 双端队列
      • 回文检查器
      • 集合结构
      • 字典结构
      • 树结构
      • 二叉树
      • 二叉树遍历
      • 前序中序还原二叉树
      • 后序中序还原二叉树
      • 二叉树深度优先搜索
      • 二叉树广度优先搜索
      • 二叉树的比较
      • 二叉树diff算法
      • 二叉搜索树
      • 二叉搜索树常见操作
      • 平衡二叉树
      • 二叉树左单旋
      • 二叉树右单旋
      • 二叉树双旋
      • 二叉堆
      • 堆排序
      • 哈夫曼树
      • 图结构
      • 邻接矩阵
      • 邻接表
      • 构建图
      • 图的深度优先搜索
      • 图的广度优先搜索
      • 普里姆算法
      • 克鲁斯卡尔算法
      • LRU算法
      • 动态规划
      • 青蛙跳台阶
      • 不同路径问题
      • 凑零钱问题
      • 打家劫舍
      • 背包问题
      • 回溯算法
      • 数字组合
      • 电话号码字母组合
      • 复原IP地址
      • 数组子集
      • 贪心算法
      • 柠檬水找零
      • 摆动序列
      • 分发糖果
      • 跳跃游戏
    • 文档协同(12)
      • 课程说明
      • 文档协同常见方案
      • OT冲突算法
      • CRDT冲突算法
      • CRDT数据结构
      • 文章翻译
      • Yjs基础介绍
      • Yjs快速上手
      • Yjs细节补充
      • 监听机制
      • 标签页同步
      • WebSocket基础知识
    • 项目实战(1)
      • ShipAny:1小时开发海外游客「中国攻略 + AI旅游助手」SaaS网站
    • AI工具(9)
      • Claude Code 完全指南
      • ComfyUI 完全指南
      • GitHub Copilot 入门指南
      • Coze 完全指南
      • Cursor vs GitHub Copilot 全面对比
      • Cursor-完整指南
      • Dify 完全指南
      • Droid 完全指南
      • n8n 完全指南
    • Astro(5)
      • Astro 入门指南
      • Astro 路由与页面
      • Astro 组件与样式
      • Astro 内容集合
      • Astro 集成与部署
    • CSS(25)
      • CSS 选择器基础
      • CSS 盒模型
      • CSS 定位与层叠
      • 浮动与 BFC
      • Flexbox 布局
      • Grid 布局
      • 响应式设计
      • 伪类与伪元素
      • 现代选择器
      • CSS 变量
      • 层叠层
      • 过渡效果
      • CSS 动画
      • 变换与滤镜
      • 容器查询
      • 文字与排版
      • 背景与渐变
      • 滚动与溢出
      • 裁剪与遮罩
      • 颜色系统
      • 边框与阴影
      • 逻辑属性
      • CSS 函数
      • 多列布局
      • 用户交互
    • ES6+(40)
      • let 和 const 命令
      • 解构赋值基础
      • 解构赋值进阶
      • 字符串的扩展
      • 字符串新增方法
      • 正则表达式扩展
      • 数值的扩展
      • BigInt 大整数
      • 函数默认参数
      • rest 参数与扩展运算符
      • 箭头函数详解
      • 尾调用优化
      • 数组扩展方法
      • 数组新增方法
      • 数组不可变方法
      • 对象的扩展
      • Object 新增方法
      • Symbol 基础
      • Symbol 进阶应用
      • Set 数据结构
      • Map 数据结构
      • TypedArray 类型化数组
      • Proxy 基础
      • Proxy 进阶应用
      • Reflect 元编程
      • 元编程实战案例
      • 装饰器 Decorator
      • Iterator 迭代器
      • Generator 基础
      • Generator 进阶
      • Promise 基础
      • Promise 进阶
      • async 函数
      • await 与错误处理
      • 异步迭代
      • Class 基础
      • Class 继承
      • Module 语法
      • Module 加载
      • ES 新特性汇总
    • HTML(8)
      • HTML 入门:构建 Web 的基石
      • HTML5 语义化:让页面更有意义
      • HTML 表单基础:用户交互的起点
      • HTML 表单进阶:验证与现代特性
      • HTML 多媒体:音视频与响应式图片
      • HTML APIs(上):客户端存储完全指南
      • HTML APIs(下):交互与设备能力
      • Canvas 与 SVG:Web 图形绘制入门
    • JavaScript(30)
      • JavaScript 简介
      • 变量与数据类型
      • 运算符
      • 类型转换
      • 控制流语句
      • 函数基础
      • 对象基础
      • 数组基础
      • 原型与原型链
      • this 关键字
      • 面向对象编程
      • 作用域
      • 闭包
      • 执行上下文
      • DOM 选择器
      • DOM 操作
      • 事件处理
      • 事件委托
      • 错误处理
      • 调试技巧
      • 正则表达式
      • Date 日期对象
      • Math 对象
      • 定时器
      • JSON 处理
      • BOM 基础
      • 本地存储
      • 表单处理
      • AJAX 与 Fetch
      • 严格模式
    • Langchain(37)
      • Langchain基本介绍
      • 模型接口
      • 缓存
      • 提示词模板
      • 结构化提示词
      • Message消息对象
      • 消息占位
      • LCEL
      • 并发流式返回结果
      • Runnable接口
      • 条件路由
      • 字符串解析器
      • 结构化输出解析器
      • 列表输出解析器
      • JSON解析器
      • 正则解析器
      • 本地资源Loader
      • Web资源Loader
      • 文本切割
      • 嵌入处理
      • 内存型向量库
      • 检索优化
      • RAG实践
      • Memory基础
      • 自动维护聊天记录
      • 实现长程记忆
      • 持久化记忆
      • 第三方存储
      • Tools基础
      • Tools配置
      • Zod基础
      • LangChain中使用工具
      • 重新认识Agent
      • LangChain中创建Agent
      • Agent实践
      • 回退机制
      • 回调机制
    • LLM(34)
      • LLM发展历史
      • N-Gram模型
      • 词袋模型
      • 词元
      • 嵌入
      • Transformer内部结构
      • Transformer解码策略
      • Transformer块
      • 本地部署大模型
      • Agent
      • 流式返回信息
      • 支持上下文
      • 获取实时消息
      • 外接DeepSeek
      • Function Calling
      • MCP理论知识
      • Resources
      • Schema
      • 注册资源模板
      • Prompts
      • 监听资源更新
      • 远程通信方式
      • MCP Client
      • 大模型幻觉
      • RCT构词法
      • 提示词常见技巧
      • 基于反馈迭代优化
      • 改善提示效果
      • 提示词评估工具
      • RAG基础知识
      • RAG实践
      • RAG架构演进
      • 大模型微调
      • LoRA微调
    • MCP(8)
      • MCP 入门:连接 AI 与外部世界的桥梁
      • 搭建第一个 MCP Server
      • MCP Tools 深入:让 AI 具备行动能力
      • MCP Resources:向 AI 暴露数据
      • MCP Prompts:预定义 AI 交互模板
      • MCP Transport 与部署
      • 构建 MCP Client:集成到你的应用
      • 实战:构建文档助手 MCP Server
    • Nest(31)
      • 快速上手Nest Cli
      • 三层架构与MVC
      • IoC
      • Nest中的装饰器
      • 模块的基本概念
      • 什么是AOP
      • 中间件与守卫
      • 拦截器
      • 文件上传
      • 自定义装饰器
      • mysql相关回顾
      • 对于ORM的理解
      • 快速上手Redis
      • Nest中使用Redis
      • 状态保存方式
      • Nest中实现Session与JWT
      • JWT登录注册后端处理
      • 使用Passport与循环引用问题
      • 通过环境变量获取配置信息
      • RBAC权限设计1
      • RBAC权限设计2
      • Nest中使用Websocket
      • 日志处理
      • 面向切面的日志处理
      • 快速入门GraphQL
      • Nest中使用GraphQL
      • Swagger API文档
      • 单元测试与E2E测试
      • 安全防护实践
      • 定时任务与任务调度
      • Bull队列与异步任务
    • Next(36)
      • Next.js 简介
      • 项目创建与配置
      • App Router 概述
      • 创建第一个页面
      • 开发工具与调试
      • TypeScript 配置
      • 文件系统路由
      • 动态路由
      • 嵌套路由与布局
      • 路由组
      • 并行路由
      • 拦截路由
      • 导航与链接
      • 中间件
      • Server Components
      • Client Components
      • 混合渲染模式
      • Streaming 与 Suspense
      • 静态与动态渲染
      • 数据获取基础
      • 缓存策略
      • Server Actions
      • Route Handlers
      • 数据库集成
      • 错误处理
      • 图片优化
      • 字体优化
      • 脚本优化
      • 元数据与 SEO
      • 性能分析
      • 认证基础
      • NextAuth 集成
      • 环境变量与配置
      • 测试策略
      • 部署方案
      • 项目实战
    • Nuxt(36)
      • web应用变迁史
      • 课程说明
      • SSR初体验
      • 水合操作
      • 改造CSR项目
      • 基本介绍
      • 配置概览
      • 组件类型
      • Nuxt模块
      • 嵌套路由
      • 动态路由
      • 请求数据
      • server目录
      • 应用部署
      • 中间件
      • 插件
      • layers
      • 过渡
      • 状态管理
      • 全匹配路由
      • 路由补充知识
      • 自定义路由
      • 自定义模块
      • 自定义指令
      • meta相关处理
      • 渲染模式
      • 错误处理
      • 不同阶段错误
      • 客户端渲染组件
      • 其余内置组件
      • 配置相关细节
      • 常用组合式函数
      • 服务器引擎
      • NuxtUI
      • SSR注意事项
      • 课程收官
    • React(55)
      • 序章:React课程介绍
      • React 基本介绍
      • JSX 基础语法
      • 组件与事件绑定
      • 组件状态与数据传递
      • 表单
      • 生命周期
      • Hooks
      • React-router介绍
      • React-router v版本学生管理系统课堂笔记
      • React-redux 介绍
      • Antd介绍
      • coder station前台系统笔记
      • coder station 后台系统笔记
      • Antd Pro介绍
      • Dva介绍
      • Umi.js 介绍
      • 属性默认值和类型验证
      • 高阶组件
      • Ref
      • Context
      • Render Props
      • Portals
      • 错误边界
      • 组件渲染性能优化
      • 前端框架的理解
      • Scheduler调度延时任务
      • 最小堆
      • React中的位运算
      • beginWork工作流程
      • completeWork工作流程
      • 图解diff算法
      • commit 工作流程
      • lane模型
      • React 中的事件
      • Hooks原理
      • React和Vue描述页面的区别
      • useState和useReducer
      • effect相关hook
      • useCallback和useMemo
      • useRef
      • Update
      • 性能优化策略之eagerState
      • 性能优化策略之bailout
      • bailout和ContextAPI
      • 性能优化对日常开发启示
      • 前端框架的分类
      • 虚拟 DOM
      • React 整体架构
      • React 渲染流程
      • Fiber双缓冲
      • MessageChannel
      • Scheduler调度普通任务
      • 课程介绍
      • 课程总结
    • ReactNative(33)
      • RN 基本介绍
      • 快速搭建 RN 开发环境
      • 复习 React 基础知识
      • RN 基础知识
      • 实战案例
      • RN 内置基础组件
      • 容器组件
      • Pressable 组件
      • 列表组件
      • 功能组件
      • 第三方组件库
      • 自定义组件案例:弹框组件
      • 自定义组件案例:单选组件
      • 基础API
      • 屏幕API
      • 设备API
      • 动画API
      • 手势API
      • React Navigation简介
      • 参数传递和标题栏信息配置
      • 嵌套路由与生命周期
      • 其他类型的导航
      • 状态管理介绍
      • Redux
      • 其他第三方库
      • Expo SDK
      • Fabric 架构
      • 渲染、提交和挂载
      • 实现跨平台
      • 扁平化视图
      • 线程模型
      • 如何打包发布应用
      • 通过 React Native CLI 搭建环境
    • TailwindCSS(7)
      • TailwindCSS基础介绍
      • 环境搭建与配置
      • 布局系统
      • 间距与尺寸系统
      • 颜色与主题
      • 响应式设计
      • 交互与动画
    • TypeScript(35)
      • 初识 TypeScript
      • 基础类型
      • 数组与元组
      • 对象类型
      • 函数类型
      • 类型推断与断言
      • 联合类型与交叉类型
      • 字面量类型与类型别名
      • 接口基础
      • 接口进阶
      • 接口继承与实现
      • 类基础
      • 类的访问修饰符
      • 类的继承
      • 类与接口对比
      • 泛型基础
      • 泛型接口与泛型类
      • 泛型约束
      • 内置工具类型(上)
      • 内置工具类型(下)
      • 泛型实战
      • 类型守卫
      • 类型收窄
      • 索引类型与索引签名
      • 映射类型
      • 条件类型
      • infer 关键字
      • 模板字面量类型
      • 模块系统
      • 命名空间
      • 声明文件基础
      • 声明文件进阶
      • tsconfig 配置详解
      • 类型体操入门
      • TypeScript 最佳实践
    • 杂谈(9)
      • Cloudflare 全栈部署指南
      • Creem 支付集成指南
      • Dokploy 自托管部署指南
      • GitHub OAuth 登录集成指南
      • Google OAuth 登录集成指南
      • Stripe 支付集成指南
      • Supabase 完全指南
      • Vercel 自动化部署指南
      • Zeabur 部署指南

    Zod基础

    2025年9月28日 · 28 min
    ChatGPTClaude

    Zod解决了什么痛点?

    TypeScript 能帮助我们在编译阶段发现类型错误,提升代码的健壮性。

    但是有一个问题:运行时的数据并不会自动带上类型信息。例如 API 返回的数据、用户输入的表单、第三方库的结果,这些都是动态数据。

    这就是痛点:TS 的类型信息在编译时有用,但到运行时就丢失了。

    Zod 是一个 TypeScript 优先的 schema 声明与验证库。它的目标就是把 “类型定义” 和 “运行时校验” 结合在一起,让你写一次,就能同时获得:

    • 运行时验证(通过 parse/safeParse 检查输入数据是否符合预期)
    • TypeScript 类型推断(无需重复写 interface,自动生成 TS 类型)

    Zod 的核心特点

    Zod 具备以下几个关键特性:

    • TypeScript 优先:Schema 与 TS 类型深度集成。你定义 schema 的同时,Zod 会自动推导出 TS 类型。
    • 声明即校验:所有类型都是通过 z.* 构造函数声明出来的,例如 z.string()、z.number(),同时可直接用来解析数据。
    • 无第三方依赖:Zod 是纯 TypeScript 实现,无需额外 runtime。
    • 组合性强:Schema 可以组合、扩展、变换,灵活应对复杂数据结构。
    • 错误信息清晰:返回的错误包含详细的 path 与 message,支持自定义错误提示与格式化。

    快速上手

    1. 安装
    2. 定义 Schema
    3. 校验数据

    常用Schema类型

    1. 原始类型:string, number, boolean, bigint, date, undefined, null, any, unknown, never
    2. 对象:z.object
    3. 数组与元组:z.array, z.tuple
    4. 集合类型:z.record, z.map, z.set
    5. 字面量与枚举:z.literal, z.enum, z.nativeEnum

    组合与修饰

    • .optional() 表示:这个值可以是指定类型,也可以是 undefined。适用于对象中非必填字段的场景。
    • .nullable() 允许一个值是 null 或指定类型。
    • .nullish() 允许一个值是 null、undefined 或指定类型。
    • .default() 为一个 schema 设置默认值。
    • z.union 联合类型表示“值可以是几种类型之一”。
    • z.intersection 交叉类型表示“必须同时满足两个 schema”。

    校验器

    1. 内置校验器

      • .min(len) / .max(len):长度限制

      • .email():邮箱格式

      • .url():URL 格式

      • .uuid():UUID 格式

      • .regex(regexp):正则匹配

    2. 自定义校验:当内置方法不够用时,可以用 .refine() 添加自定义校验逻辑。

      • 接收一个布尔返回的函数(true 表示校验通过)。
      • 可以自定义错误消息。
      const password = z.string().refine((val) => val.length >= 8, {
      message: '密码至少 8 位',
      })
      password.parse('12345678') // ✅
      password.parse('123') // ❌ 报错:密码至少 8 位

    与 TS 的集成

    每个 schema 都可以通过 z.infer 推断对应的 TypeScript 类型。

    const User = z.object({
    name: z.string(),
    age: z.number(),
    })
    // 推断出静态类型
    type UserType = z.infer<typeof User>
    // 等价于:{ name: string; age: number }

    Zod介绍

    在日常开发中,我们往往使用 TypeScript 来做静态类型检查。TypeScript 能帮助我们在编译阶段发现类型错误,提升代码的健壮性。但是有一个问题:运行时的数据并不会自动带上类型信息。例如 API 返回的数据、用户输入的表单、第三方库的结果,这些都是动态数据。

    来看一个具体的例子。假设你在 TypeScript 里写了:

    interface User {
    name: string
    age: number
    }

    那么这能够在编译时拦截住一些错误,例如:

    const u: User = {
    name: 42, // 报错,应该是 string
    age: "hi", // 报错,应该是 number
    };

    但是,如果数据是运行时动态获取的,比如从接口返回、用户输入,通常你会这样写:

    const user = JSON.parse('{"name": 42, "age": "hi"}')

    注意:JSON.parse 的返回值在 TypeScript 里是 any。也就是说编译器根本不知道 user 里面具体是什么类型。

    你甚至可以这样写:

    const user: User = JSON.parse('{"name": 42, "age": "hi"}')

    TypeScript 不会报错!它只会认为:“既然你告诉我这是 User,那我就信了”。但实际运行时,user.name 是 42(数字),user.age 是 "hi"(字符串),跟定义完全相反。

    这就是痛点:TS 的类型信息在编译时有用,但到运行时就丢失了。

    Zod 是一个 TypeScript 优先的 schema 声明与验证库。它的目标就是把 “类型定义” 和 “运行时校验” 结合在一起,让你写一次,就能同时获得:

    • 运行时验证(通过 parse/safeParse 检查输入数据是否符合预期)
    • TypeScript 类型推断(无需重复写 interface,自动生成 TS 类型)

    官方文档的首页开宗明义地强调:

    Zod is a TypeScript-first schema declaration and validation library. Its goal is to make it as easy as possible to validate runtime values with a well-typed, composable schema.

    Zod 的核心特点

    Zod 具备以下几个关键特性:

    • TypeScript 优先:Schema 与 TS 类型深度集成。你定义 schema 的同时,Zod 会自动推导出 TS 类型。
    • 声明即校验:所有类型都是通过 z.* 构造函数声明出来的,例如 z.string()、z.number(),同时可直接用来解析数据。
    • 无第三方依赖:Zod 是纯 TypeScript 实现,无需额外 runtime。
    • 组合性强:Schema 可以组合(union、intersection)、扩展(extend)、变换(transform/pipe),灵活应对复杂数据结构。
    • 错误信息清晰:返回的错误包含详细的 path 与 message,支持自定义错误提示与格式化。

    快速上手

    首先是安装,选择一个你喜欢的包管理器来安装它:

    Terminal window
    npm install zod

    定义 Schema

    Zod 的核心理念是:先定义 Schema,再用 Schema 去校验数据。Schema 通过 z.* 系列方法来构建。

    例如:

    import { z } from 'zod'
    // 一个字符串 schema
    const Name = z.string()
    // 一个数字 schema
    const Age = z.number()
    // 一个对象 schema
    const User = z.object({
    name: Name,
    age: Age,
    })

    此时 User 就代表“一个对象,里面有 name: string 和 age: number”。

    校验数据

    定义完 Schema 后,就可以用 .parse() 或 .safeParse() 校验数据。

    1. .parse() —— 抛异常方式

      User.parse({ name: 'Alice', age: 20 })
      // 返回 { name: "Alice", age: 20 }
      User.parse({ name: 42, age: 'hi' })
      // 直接抛出 ZodError

      来看一下这个 ZodError 的具体信息:

      ZodError: [
      {
      expected: 'string',
      code: 'invalid_type',
      path: ['name'],
      message: 'Invalid input: expected string, received number',
      },
      {
      expected: 'number',
      code: 'invalid_type',
      path: ['age'],
      message: 'Invalid input: expected number, received string',
      },
      ]
    2. .safeParse() —— 返回结果对象

      成功示例

      const result = User.safeParse({ name: '张三', age: 18 })

      返回内容:

      { success: true, data: { name: '张三', age: 18 } }

      失败示例:

      const result = User.safeParse({ name: 18, age: '张三' })

      返回内容:

      {
      success: false,
      error: ZodError: [
      {
      "expected": "string",
      "code": "invalid_type",
      "path": [
      "name"
      ],
      "message": "Invalid input: expected string, received number"
      },
      {
      "expected": "number",
      "code": "invalid_type",
      "path": [
      "age"
      ],
      "message": "Invalid input: expected number, received string"
      }
      ]
      }

    常用 Schema 类型

    接下来我们来看一下 Zod 中提供的常见的 Schema 类型。总结起来如下:

    1. 原始类型:string, number, boolean, bigint, date, undefined, null, any, unknown, never
    2. 对象:z.object
    3. 数组与元组:z.array, z.tuple
    4. 集合类型:z.record, z.map, z.set
    5. 字面量与枚举:z.literal, z.enum, z.nativeEnum

    1. 原始类型

    字符串

    const str = z.string()
    str.parse('hello') // 通过
    str.parse(123) // 抛错:Expected string, received number

    数字

    const num = z.number()
    num.parse(42) // 通过
    num.parse('42') // 抛错

    布尔值

    const flag = z.boolean()
    flag.parse(true) // 通过
    flag.parse('true') // 抛错

    BigInt

    const big = z.bigint()
    big.parse(100n) // 通过
    big.parse(100) // 抛错

    Date

    const date = z.date()
    date.parse(new Date()) // 通过
    date.parse('2025-01-01') // 抛错

    特殊类型

    • z.undefined()
    • z.null()
    • z.any()
    • z.unknown()
    • z.never()

    它们分别对应 JavaScript 的 undefined、null,以及 TypeScript 中的 any/unknown/never。

    2. 对象类型

    对象是 Zod 最常用的结构化类型:

    const User = z.object({
    name: z.string(),
    age: z.number(),
    })
    User.parse({ name: 'Alice', age: 20 }) // 通过
    User.parse({ name: 'Bob' }) // 抛错

    对象类型还可以:

    • 扩展:User.extend({ email: z.string() })
    • 部分可选:User.partial()
    • 必填化:User.required()

    这些 API 可以参阅 官方文档的 Objects 部分。

    3. 数组与元组

    数组

    const numArray = z.array(z.number())
    numArray.parse([1, 2, 3]) // 通过
    numArray.parse(['a', 'b']) // 抛错

    元祖

    元组与数组不同,它定义了固定长度和类型顺序:

    const tuple = z.tuple([z.string(), z.number()])
    tuple.parse(['hello', 123]) // 通过
    tuple.parse([123, 'hello']) // 抛错:顺序错误

    4. 集合类型

    Record

    键值对对象,键必须是字符串或 number,值由你指定一个 schema 来约束。换句话说,z.record() 就是一个“动态对象”,所有属性的类型都一样。

    示例:

    const NumKeyToStringVal = z.record(z.number(), z.string())

    含义:

    • 键必须是数字
    • 值必须是字符串

    所以:

    NumKeyToStringVal.parse({ 1: 'one', 2: 'two' })
    // 合法
    NumKeyToStringVal.parse({ 1: 100 })
    // 报错:Expected string, received number

    和 z.object() 的区别

    • z.object({...}):适合“固定字段”的对象,比如 name, age。
    • z.record():适合“任意字段”的对象,但要求所有键或所有值必须满足同一种类型。

    Map

    const stringToNumberMap = z.map(z.string(), z.number())
    stringToNumberMap.parse(new Map([['a', 1]])) // 通过

    Set

    const numSet = z.set(z.number())
    numSet.parse(new Set([1, 2, 3])) // 通过
    numSet.parse(new Set(['a'])) // 抛错

    5. 字面量与枚举

    字面量

    const literal42 = z.literal(42)
    literal42.parse(42) // 通过
    literal42.parse(7) // 抛错

    枚举

    const Direction = z.enum(['North', 'South', 'East', 'West'])
    Direction.parse('North') // 通过
    Direction.parse('Other') // 抛错

    原生枚举

    可以直接校验 TypeScript 的 enum:

    enum Fruits {
    Apple,
    Banana,
    }
    const FruitEnum = z.nativeEnum(Fruits)
    FruitEnum.parse(Fruits.Apple) // 通过
    FruitEnum.parse(0) // 通过
    FruitEnum.parse('Mango') // 抛错

    这些常用的 Schema 类型是 Zod 的基石,后面的组合、校验、自定义逻辑,都是基于这些基础类型构建出来的。

    组合与修饰

    在真实项目里,数据结构并不总是“简单的固定字段”。有时字段是可选的,有时允许为空,有时需要默认值,有时需要把多个 schema 组合在一起。Zod 提供了一系列组合与修饰的工具来应对这些情况。

    可选

    .optional() 表示:这个值可以是指定类型,也可以是 undefined。适用于对象中 非必填字段 的场景。

    const optionalString = z.string().optional()
    optionalString.parse('hello') // ✅ "hello"
    optionalString.parse(undefined) // ✅ undefined
    optionalString.parse(42) // ❌ Expected string, received number

    可空

    .nullable() 允许一个值是 null 或指定类型:

    const nullableString = z.string().nullable()
    nullableString.parse('hi') // ✅ "hi"
    nullableString.parse(null) // ✅ null
    nullableString.parse(undefined) // ❌

    .nullish() 允许一个值是 null、undefined 或指定类型:

    const nullishString = z.string().nullish()
    nullishString.parse('hi') // ✅ "hi"
    nullishString.parse(null) // ✅ null
    nullishString.parse(undefined) // ✅ undefined

    默认值

    .default() 为一个 schema 设置默认值:

    const stringWithDefault = z.string().default('default')
    stringWithDefault.parse('hello') // ✅ "hello"
    stringWithDefault.parse(undefined) // ✅ "default"

    .default() 仅在输入值为 undefined 时生效。如果输入是 null,依然会报错,除非你同时加了 .nullable()。

    联合类型

    z.union 联合类型表示“值可以是几种类型之一”。

    const stringOrNumber = z.union([z.string(), z.number()])
    stringOrNumber.parse('hi') // ✅
    stringOrNumber.parse(42) // ✅
    stringOrNumber.parse(true) // ❌

    交叉类型

    z.intersection 交叉类型表示“必须同时满足两个 schema”。

    const hasName = z.object({ name: z.string() })
    const hasAge = z.object({ age: z.number() })
    const Person = z.intersection(hasName, hasAge)
    Person.parse({ name: 'Alice', age: 20 }) // ✅
    Person.parse({ name: 'Bob' }) // ❌ 缺少 age

    判别联合

    当对象有一个“判别字段”时,可以用 z.discriminatedUnion 来更高效地验证。

    那什么是“判别字段”呢?

    假设你要描述一个“形状 (Shape)”,可能有 圆形 (Circle) 或 正方形 (Square)。这两种形状的数据结构不一样:

    • 圆形:需要 radius 半径
    • 正方形:需要 side 边长

    为了区分不同形状,我们常会加一个固定字段 kind:

    // 圆形
    { kind: "circle", radius: 10 }
    // 正方形
    { kind: "square", side: 5 }

    这里的 kind 就是“判别字段”,因为它的值决定了是哪种对象。

    明白了“判别字段”的含义后,接下来我们来看一下如果不用判别联合会怎样。假设我们用普通的 z.union:

    const ShapeUnion = z.union([
    z.object({ kind: z.literal('circle'), radius: z.number() }),
    z.object({ kind: z.literal('square'), side: z.number() }),
    ])

    它能区分,但在错误提示和性能上不太好。比如:

    ShapeUnion.parse({ kind: 'triangle', base: 3 })

    报错会很笼统:“不符合第一个 schema,也不符合第二个 schema”。

    接下来我们改成用 z.discriminatedUnion("kind", [...]):

    const Circle = z.object({
    kind: z.literal('circle'),
    radius: z.number(),
    })
    const Square = z.object({
    kind: z.literal('square'),
    side: z.number(),
    })
    const Shape = z.discriminatedUnion('kind', [Circle, Square])
    Shape.parse({ kind: 'circle', radius: 10 }) // ✅ 圆形
    Shape.parse({ kind: 'square', side: 5 }) // ✅ 正方形
    Shape.parse({ kind: 'triangle', base: 3 }) // ❌ 报错更清晰

    报错信息会直接告诉你:kind 字段必须是 "circle" 或 "square",而不是 "triangle"。这样比普通 union 的报错更直观。

    校验与错误处理

    目前我们了解了 Schema 和组合类型,实际项目里往往需要更复杂的数据校验规则,以及更清晰的错误处理方式。接下来我们就来看一下 Zod 的 内置校验器、自定义校验 和 **错误对象 **。

    内置校验器

    Zod 在常见类型上提供了丰富的内置校验方法。

    示例一:字符串示例

    const username = z
    .string()
    .min(3)
    .max(10)
    .regex(/^[a-z]+$/)
    username.parse('bob') // ✅
    username.parse('a') // ❌ 长度过短
    username.parse('HELLO') // ❌ 不符合正则

    部分常用方法:

    • .min(len) / .max(len):长度限制
    • .email():邮箱格式
    • .url():URL 格式
    • .uuid():UUID 格式
    • .regex(regexp):正则匹配

    示例二:数字示例

    const age = z.number().min(0).max(150).int()
    age.parse(25) // ✅
    age.parse(-1) // ❌ 小于 0
    age.parse(200) // ❌ 大于 150
    age.parse(3.14) // ❌ 不是整数

    自定义校验

    当内置方法不够用时,可以用 .refine() 添加自定义校验逻辑。

    特点:

    • 接收一个布尔返回的函数(true 表示校验通过)。
    • 可以自定义错误消息。

    例如:

    const password = z.string().refine((val) => val.length >= 8, {
    message: '密码至少 8 位',
    })
    password.parse('12345678') // ✅
    password.parse('123') // ❌ 报错:密码至少 8 位

    跨字段校验

    如果需要在对象内部做多个字段之间的校验,可以用 .superRefine()。

    特点:

    • ctx.addIssue() 用来添加自定义错误。

    • 可以设置 path 指明错误属于哪个字段。

    const PasswordSchema = z
    .object({
    pwd: z.string(),
    confirm: z.string(),
    })
    .superRefine((data, ctx) => {
    if (data.pwd !== data.confirm) {
    ctx.addIssue({
    code: 'custom',
    message: '两次密码输入不一致',
    path: ['confirm'], // 指定错误位置
    })
    }
    })

    .superRefine() 本质上比 .refine() 更灵活,能访问整个对象。

    自定义错误消息

    大多数内置校验方法可以传入 message 参数:

    const username = z.string().min(3, { message: '用户名至少 3 个字符' })
    username.parse('a')
    // 报错信息:用户名至少 3 个字符

    错误格式化

    Zod 提供了 error.format() 方法,可以把复杂的错误转换成更容易消费的格式。

    举个例子:当你用 .parse() 或 .safeParse() 校验失败时,拿到的原始错误结构是一个 ZodError,里面的 issues 是数组,每个错误都是一条记录

    const Form = z.object({
    name: z.string().min(3),
    age: z.number().min(18),
    })
    const result = Form.safeParse({ name: 'Al', age: 10 })
    if (!result.success) {
    console.log(result.error.issues)
    }

    输出如下:

    ;[
    {
    code: 'too_small',
    minimum: 3,
    type: 'string',
    inclusive: true,
    message: 'String must contain at least 3 character(s)',
    path: ['name'],
    },
    {
    code: 'too_small',
    minimum: 18,
    type: 'number',
    inclusive: true,
    message: 'Number must be greater than or equal to 18',
    path: ['age'],
    },
    ]

    输出格式上是数组形式,每个错误单独一条,包含很多元信息(code、minimum、path 等),这种结构比较全,但不太适合直接用在表单 UI。

    Zod 提供了 format() 方法,把错误按字段名组织起来,更直观。

    if (!result.success) {
    console.log(result.error.format())
    }

    输出类似这样:

    {
    "name": {
    "_errors": ["String must contain at least 3 character(s)"]
    },
    "age": {
    "_errors": ["Number must be greater than or equal to 18"]
    },
    "_errors": []
    }

    输出格式变成了对象形式,每个字段对应一个键(name、age),具体错误放在 ._errors 数组里,最外层的 _errors 保存“全局错误”。这种格式更容易在前端表单中绑定,比如直接取 error.format().name._errors[0] 就能拿到 “用户名太短” 这条提示。

    输出输入控制

    Zod 除了能做运行时校验,还提供了控制 输入值的转换 和 Schema 输出的加工 的功能。常用的有:

    • 强制类型转换 (coercion)
    • 变换 (transform)
    • 管道 (pipe)

    这些工具让我们在接收“脏输入”或需要调整数据格式时更灵活。

    强制类型转换 (coercion)

    Zod 提供了 z.coerce.* 系列方法,可以把输入强制转换成目标类型。

    举个例子:

    const coercedNumber = z.coerce.number()
    coercedNumber.parse('42') // ✅ 42 (string → number)
    coercedNumber.parse(42) // ✅ 42
    coercedNumber.parse(true) // ❌ 报错

    支持的类型包括:

    • z.coerce.string()
    • z.coerce.number()
    • z.coerce.boolean()
    • z.coerce.bigint()
    • z.coerce.date()

    典型应用场景:从 URL 查询参数、表单输入中获取的数据通常是字符串,需要转成 number、date 等。

    变换 (transform)

    .transform() 允许你在校验通过后对值做进一步处理。也就是说,分为下面 2 个步骤:

    1. 输入先经过 schema 校验
    2. 校验通过后进入 .transform() 回调,返回新值
    const trimmedString = z.string().transform((val) => val.trim())
    trimmedString.parse(' hello ') // ✅ "hello"

    你可以用它做各种映射,比如格式化字符串、计算新字段等。

    管道 (pipe)

    .pipe() 用于把多个 schema 串联起来,相当于一个“管道”。

    工作流程:

    1. 输入先经过前一个 schema 校验
    2. 结果再传给下一个 schema
    3. 最终得到输出

    和 .transform() 的区别:

    • .transform() 回调里你自己写逻辑
    • .pipe() 是把 schema 串在一起,复用已有规则
    const numberToString = z.number().pipe(z.string())
    numberToString.parse(123) // ✅ "123"

    类型推断与 TS 集成

    Zod 最大的优势之一,就是它与 TypeScript 深度集成。你只需定义一次 Schema,就能同时获得运行时校验和编译时类型检查。这避免了“写两遍类型”的重复劳动。

    1. 基础推断:z.infer

    每个 schema 都可以通过 z.infer 推断对应的 TypeScript 类型。

    特点:

    • z.infer 自动把 schema 转换为 TypeScript 类型
    • 保证 schema 定义与 TS 类型始终一致
    const User = z.object({
    name: z.string(),
    age: z.number(),
    });
    // 推断出静态类型
    type UserType = z.infer<typeof User>;
    // 等价于:{ name: string; age: number }

    2. 区分输入与输出:z.input 与 z.output

    有些 schema 会做转换 (transform/pipe),导致输入和输出类型不同。这种情况下,可以用 z.input 和 z.output 明确区分。

    const Schema = z.string().transform((val) => val.length);
    type In = z.input<typeof Schema>; // string
    type Out = z.output<typeof Schema>; // number

    In 表示 parse 之前的数据类型,Out 表示 parse 之后返回的类型。

    Zod 与 TypeScript 的紧密结合,使它不仅是一个运行时校验库,更是一个类型驱动开发的利器。

    其它特性

    在掌握了基础类型、组合与校验之后,Zod 还提供了一些其它使用的特性,方便在复杂场景下保持代码清晰与灵活。常用的特性有:

    • 定义递归类型
    • 给 Schema 添加描述信息
    • 对象模式修饰

    1. 递归类型

    在 TypeScript 里,可能需要定义树形或链表结构。该情况下 schema 需要自我引用,这就可以使用 z.lazy()。它接收一个返回 schema 的函数,用于解决“类型尚未定义完成”时的自我引用问题。

    例如:

    const Category: z.ZodType<any> = z.object({
    name: z.string(),
    children: z.array(z.lazy(() => Category)),
    });
    Category.parse({
    name: "Root",
    children: [
    { name: "Child 1", children: [] },
    { name: "Child 2", children: [] },
    ],
    }); // ✅

    2. 描述信息

    给 schema 添加人类可读的描述信息。描述不会影响校验逻辑,但在错误信息或文档生成(如 JSON Schema)时很有用。

    例如:

    const User = z.object({
    name: z.string().describe('用户名'),
    age: z.number().describe('用户年龄'),
    })

    3. 对象模式修饰

    对象 schema 默认只校验声明过的字段,多余字段会被保留。Zod 提供了三种模式修饰:

    .strict():禁止额外字段。

    const User = z.object({ name: z.string() }).strict()
    User.parse({ name: 'Alice' }) // ✅
    User.parse({ name: 'Bob', age: 20 }) // ❌ 不允许额外字段

    .passthrough():允许额外字段并保留。

    const User = z.object({ name: z.string() }).passthrough()
    User.parse({ name: 'Bob', age: 20 })
    // ✅ { name: "Bob", age: 20 }

    .strip():允许额外字段,但自动去掉。

    const User = z.object({ name: z.string() }).strip()
    User.parse({ name: 'Bob', age: 20 })
    // ✅ { name: "Bob" } (age 被去掉)

    与生态集成

    Zod 除了能在代码中做数据校验,还能与其他工具链结合使用,常用于 接口文档、API 校验、表单校验 等场景。这里简单举两个生态集成的例子。

    1. 导出 JSON Schema

    Zod v4 开始,内置了 toJSONSchema 功能,可以把 schema 转换为 JSON Schema。

    import * as z from 'zod'
    const User = z.object({ name: z.string(), age: z.number().min(18) })
    const jsonSchema = z.toJSONSchema(User)
    console.log(JSON.stringify(jsonSchema, null, 2))

    常用于:

    • 生成 API 文档
    • 在配置文件中提供 schema 验证
    • 与第三方工具(如 AJV、OpenAPI 生成器)结合

    2. 与表单库结合

    Zod 也常用于前端表单校验,比如配合 React Hook Form。

    import { useForm } from 'react-hook-form'
    import { zodResolver } from '@hookform/resolvers/zod'
    const schema = z.object({
    email: z.string().email(),
    password: z.string().min(8),
    })
    function LoginForm() {
    const {
    register,
    handleSubmit,
    formState: { errors },
    } = useForm({
    resolver: zodResolver(schema),
    })
    return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
    <input {...register('email')} />
    {errors.email && <span>{errors.email.message}</span>}
    <input {...register('password')} type="password" />
    {errors.password && <span>{errors.password.message}</span>}
    <button type="submit">提交</button>
    </form>
    )
    }

    上面代码 zodResolver 直接把 Zod schema 嵌入表单校验流程,错误信息会自动传递到 formState.errors,方便 UI 展示。

    Zod 4 与 Mini

    Zod 在 v4 版本进行了重要更新,同时官方也提供了一个极简版本 Zod Mini

    先来看一下 v4 相比 v3 的变化:

    • TypeScript 5.5+ 支持:Zod 4 依赖新版 TypeScript 的类型推导能力。
    • JSON Schema 导出:schema 可以直接调用 .toJSONSchema()。
    • 更完善的错误处理:错误信息结构化更清晰,支持更细粒度的格式化。
    • 包体优化:内部 API 做了调整,去掉了部分冗余。

    官方迁移文档明确说明:大多数 v3 代码可以无缝迁移到 v4,只有少数 API 需要更新。

    接下来说一下 Zod Mini,这是 Zod 的一个“精简子集”:

    • 定位:体积更小,加载更快,功能覆盖日常 80% 的校验场景。
    • 包含:字符串、数字、布尔值、数组、对象、联合等核心类型。
    • 不包含:部分高级特性(如 .transform()、递归类型、JSON Schema 导出等)。

    适用场景:

    • 浏览器端(尤其是移动端),对包体积非常敏感;
    • 只需要基础校验,不依赖高级功能。

    写在最后

    学习 Zod,不仅仅是掌握了一个数据校验库的 API。更重要的是,它让我们重新思考了“类型”与“数据”之间的关系。

    TypeScript 给了我们编译期的安全感,但运行时的数据往往来自不可控的环境:接口、用户输入、配置文件……这些地方的数据总是“不听话”。Zod 的价值,就在于它把这两个世界桥接了起来:写一次定义,编译时有类型,运行时能验证。

    在实际开发中,这种一致性带来的不仅是代码质量的提升,更是一种心态上的踏实。你不用再担心后端返回的数据有没有字段缺失,也不用害怕表单输入乱七八糟。因为有了 Zod,你能把“不确定”关在门外,把“确定”握在手中。

    当然,Zod 不是银弹。它无法替你决定业务逻辑的正确性,但它能让你的代码更清晰,让错误更早暴露,让协作更顺畅。这已经足够让它成为现代前端与全栈开发中不可或缺的一环。

    如果说 TypeScript 让我们写得安心,那么 Zod 则让我们运行得放心。

    返回 BlogLangchain
    上一篇Tools配置下一篇LangChain中使用工具
    Skip toc
    • Zod介绍
    • 快速上手
    • 常用 Schema 类型
      • 1. 原始类型
      • 2. 对象类型
      • 3. 数组与元组
      • 4. 集合类型
      • 5. 字面量与枚举
    • 组合与修饰
    • 校验与错误处理
    • 输出输入控制
    • 类型推断与 TS 集成
    • 其它特性
    • 与生态集成
    • Zod 4 与 Mini
    • 写在最后
    Table of Contents
    • Zod介绍
    • 快速上手
    • 常用 Schema 类型
      • 1. 原始类型
      • 2. 对象类型
      • 3. 数组与元组
      • 4. 集合类型
      • 5. 字面量与枚举
    • 组合与修饰
    • 校验与错误处理
    • 输出输入控制
    • 类型推断与 TS 集成
    • 其它特性
    • 与生态集成
    • Zod 4 与 Mini
    • 写在最后
    > share on twitter