gvergnaud/ts-pattern · 上手攻略
- 仓库:gvergnaud/ts-pattern
- 链接:https://github.com/gvergnaud/ts-pattern
- 分类:ai · TypeScript 类型编程
- 作者:Tom
- 更新:2026-7-14
是什么
ts-pattern 是一个 TypeScript 的穷举式模式匹配(Pattern Matching)库,由 Gabriel Vergnaud 开发,当前版本 5.9.0(2025年10月发布),npm 周下载量超过 400 万次,GitHub 星标 15k+。
它用用户态(userland)实现的方式,为 TypeScript 带来了类似 Rust、Swift、Elixir、Haskell 等语言内置的模式匹配能力——包括穷尽性检查(exhaustiveness checking)和智能类型收窄。TC39 的 ECMAScript 模式匹配提案目前仍处于 Stage 1,距离落地还有数年,ts-pattern 让你今天就能用上。
解决什么问题
- 消灭漏分支 bug:用
if/switch处理联合类型时,漏写一个分支编译器不会报错。ts-pattern的.exhaustive()要求你处理每一种可能,漏掉则类型报错。 - 替代深层嵌套的三元/if:条件复杂时,嵌套
if的缩进地狱让人头疼,模式匹配用扁平的结构表达复杂分支。 - 统一多类型数据结构的条件判断:对象、数组、元组、Set、Map 全部支持同一种 API,无需为每种类型写不同的判断代码。
- 类型安全的分支提取:
.with()的 handler 函数自动获得匹配子句的精确类型,不需要手动as转换。
快速安装
# npm
npm install ts-pattern
# pnpm
pnpm add ts-pattern
# yarn
yarn add ts-pattern
# bun
bun add ts-pattern
# JSR(用于 Deno/TypeScript 严格模式)
npx jsr add @gabriel/ts-pattern
注意:ts-pattern 5.x 要求 TypeScript 4.7+。如果项目使用更旧的 TypeScript 版本,4.x 版本仍有支持。
核心用法
基础 match
import { match, P } from 'ts-pattern';
type Data =
| { type: 'text'; content: string }
| { type: 'img'; src: string };
type Result =
| { type: 'ok'; data: Data }
| { type: 'error'; error: Error };
const result: Result = ...;
// 基础用法
const html = match(result)
.with({ type: 'error' }, () => <p>出错了</p>)
.with({ type: 'ok', data: { type: 'text' } }, (res) => <p>{res.data.content}</p>)
.with({ type: 'ok', data: { type: 'img', src: P.select() } }, (src) => <img src={src} />)
.exhaustive();
状态机/Reducer 示例
import { match, P } from 'ts-pattern';
type State =
| { status: 'idle' }
| { status: 'loading'; startTime: number }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
type Event =
| { type: 'fetch' }
| { type: 'success'; data: string }
| { type: 'error'; error: Error }
| { type: 'cancel' };
const reducer = (state: State, event: Event): State =>
match([state, event])
.returnType<State>()
.with(
[{ status: 'loading' }, { type: 'success', data: P.select() }],
([, event]) => ({ status: 'success', data: event.data })
)
.with(
[{ status: 'loading' }, { type: 'error', error: P.select() }],
(error) => ({ status: 'error', error })
)
.with(
[{ status: P.not('loading') }, { type: 'fetch' }],
() => ({ status: 'loading', startTime: Date.now() })
)
.with(
[
{ status: 'loading', startTime: P.when((t) => t + 2000 < Date.now()) },
{ type: 'cancel' },
],
() => ({ status: 'idle' })
)
.with(P._, () => state) // catch-all
.exhaustive();
常用 Pattern 语法
import { match, P } from 'ts-pattern';
// 字面量匹配
match(value)
.with({ type: 'success' }, (v) => v.data)
.with({ count: 0 }, () => 'empty')
// 通配符 P._
.with(P._, () => 'fallback')
// P.not() 排除特定值
.with({ status: P.not('loading') }, (v) => v)
// P.when() 守卫条件
.with({ age: P.when((a) => a >= 18) }, (v) => v)
// P.select() 提取子值
.with({ user: { name: P.select() } }, (name) => name)
// P.union() 或模式
.with(P.union({ type: 'a' }, { type: 'b' }), (v) => v)
// 数组/元组匹配
.with([1, P._, 3], ([first]) => first)
// P.array 匹配数组长度/元素
.with({ items: P.array({ type: 'todo' }) }, (v) => v)
// P.record 匹配对象键值
.with({ users: P.record(P.string, P.select()) }, (users) => users)
// P.instanceOf 类实例
.with(P.instanceOf(Error), (e) => e.message)
非穷举用法(不需要穷尽检查时)
// 用 .otherwise() 替代 .exhaustive()
match(result)
.with({ type: 'success' }, (r) => r.data)
.otherwise(() => null);
// 或者 .run() 直接执行
const result = match(value)
.with({ type: 'a' }, () => 1)
.with({ type: 'b' }, () => 2)
.run();
// 类型守卫 isMatching
if (isMatching({ type: 'success' }, response)) {
// response 类型自动收窄为 { type: 'success'; data: ... }
}
典型适用场景
- Redux/状态管理 reducer:每个 action 对应一种状态变更,
.exhaustive()确保新增 action 类型时不会漏掉处理分支。 - API 响应处理:后端返回多种错误码/数据形态,用模式匹配统一处理,比层层
if (res.code === ...)更清晰。 - AST 解析:编译器前端或模板引擎遍历语法树,每种节点类型有不同的处理逻辑。
- 路由匹配:配合 React Router 或类似框架,根据路径/参数结构匹配不同页面组件。
- 表单验证错误处理:不同错误类型需要渲染不同的提示 UI。
坑与注意
| 坑 | 说明 |
|---|---|
| 编译时间增加 | .exhaustive() 的穷尽检查会加重 TypeScript 编译器负担,大型 reducer 文件编译时间可能明显变长。 |
| 不支持 JSX 扩展语法(v5 变更是误传) | ts-pattern 可以配合 JSX 使用,但本身不依赖 React,任何框架均可。 |
| handler 参数推断规则复杂 | P.select() 和普通参数可能产生混淆,仔细阅读官方文档的参数推断部分。 |
| 需要维护 Pattern 类型定义 | 每次联合类型变更都需要同步更新 match 语句,工作量不小。 |
| 不支持异步 handler | 如果 handler 是 async 函数,不能直接用在 .with() 里,需要自行包装。 |
| 版本 4→5 有 breaking change | 升级前需查看 CHANGELOG,主要是一些 API 调整。 |
与同类对比
| 库 | 大小 | 穷尽检查 | 特点 |
|---|---|---|---|
| ts-pattern | ~2kB | ✅ | 最完整、生态最好、文档最详尽 |
| match-iz | 较小 | ❌ | 轻量,但功能较少 |
| exhaustive-match | 较小 | ✅ | 穷尽检查但 API 较简陋 |
| TC39 提案(未来) | 内置 | ✅ | 尚未落地,值得期待 |
ts-pattern 是目前 TypeScript 生态中功能最完整、维护最活跃的模式匹配库,400 万周下载量和 15k stars 足以说明其社区认可度。
一句话推荐结论
TypeScript 项目中处理复杂条件分支的首选库,尤其是 Redux reducer、API 响应处理、AST 遍历等场景——用 .exhaustive() 把漏分支 bug 在编译期消灭,比任何测试都更可靠。