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: ... }
}

典型适用场景

  1. Redux/状态管理 reducer:每个 action 对应一种状态变更,.exhaustive() 确保新增 action 类型时不会漏掉处理分支。
  2. API 响应处理:后端返回多种错误码/数据形态,用模式匹配统一处理,比层层 if (res.code === ...) 更清晰。
  3. AST 解析:编译器前端或模板引擎遍历语法树,每种节点类型有不同的处理逻辑。
  4. 路由匹配:配合 React Router 或类似框架,根据路径/参数结构匹配不同页面组件。
  5. 表单验证错误处理:不同错误类型需要渲染不同的提示 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 在编译期消灭,比任何测试都更可靠。