入门
本篇讲 ts-pattern 的基础用法:
match/with/exhaustive/otherwise、字面量与对象模式、最常用的P.*通配模式。版本基线 ts-pattern 5.x。对比对象:原生if/else、switch。
速查
- 安装:
npm i ts-pattern(或pnpm add ts-pattern/bun add ts-pattern) - 导入:
import { match, P } from 'ts-pattern' - 基本结构:
match(value).with(pattern, handler).exhaustive() - 兜底收尾:
.otherwise((value) => 默认值)(等价.with(P._, handler).exhaustive()) - 通配符:
P._(匹配任意值,P.any是其别名) - 类型通配:
P.string/P.number/P.boolean/P.bigint/P.nullish - 多模式(三选一走同一分支):
.with('a', 'b', 'c', () => '...') - 核心认知:match 是表达式,命中分支 handler 的返回值即整条表达式的返回值
- ⚠️ 匹配自上而下短路:第一个命中的分支胜出,
P._兜底放最后 - ⚠️
.exhaustive()做编译期穷尽检查,漏分支编译报错;.run()不做检查、不安全
一、ts-pattern 是什么
官方一句话定位:「The exhaustive Pattern Matching library for TypeScript」。三个关键点:
- 替代条件语句:用一条
match().with().exhaustive()表达式表达原本要写一堆if/else/switch的分支逻辑。 - 类型安全:每个分支命中后,handler 拿到的值被精确收窄到对应类型;末尾
.exhaustive()在编译期检查是否漏分支。 - 求值表达式:
match(...)不是语句而是表达式,整体有返回值,可直接const x = match(...)...。
边界提醒:ts-pattern 处理「已知类型的值」的分支选择,不做「未知输入」的运行时 schema 校验——那是 Zod/Valibot 的范畴。二者常配合:先 Zod 解析得到可信类型,再 ts-pattern match 它做穷尽分发。
二、第一个匹配表达式
import { match } from 'ts-pattern';
type Status = 'loading' | 'success' | 'error';
// status 是字面量联合,三个分支恰好覆盖全部可能
const getMessage = (status: Status) =>
match(status)
.with('loading', () => 'Loading...')
.with('success', () => 'Done!')
.with('error', () => 'Failed')
.exhaustive(); // 漏掉任一分支都会编译报错
getMessage('success'); // "Done!".with(pattern, handler) 的第一个参数是模式,第二个是命中后执行的处理函数;handler 的返回值就是整条表达式的返回值。
三、对象模式:匹配结构
用对象作模式时,ts-pattern 逐属性递归匹配——输入只要包含模式列出的键、且值满足子模式即命中(输入可有额外属性):
import { match } from 'ts-pattern';
type Result =
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
const render = (res: Result) =>
match(res)
// 命中后 res 被收窄到 success 成员,能安全访问 data
.with({ status: 'success' }, (r) => `数据:${r.data}`)
// 这里 r 被收窄到 error 成员,能访问 error
.with({ status: 'error' }, (r) => `出错:${r.error.message}`)
.exhaustive();类型收窄是核心:在 success 分支里访问
r.error会编译报错,因为该分支的类型上没有error字段。
四、常用通配模式
import { match, P } from 'ts-pattern';
const describe = (input: unknown) =>
match(input)
.with(P.string, (s) => `字符串(${s.length})`) // s: string
.with(P.number, (n) => `数字 ${n}`) // n: number
.with(P.boolean, () => '布尔')
.with(P.nullish, () => '空值(null/undefined)') // null | undefined
.otherwise(() => '其它');P._:匹配任意值(P.any是它的别名)。P.string/P.number/P.boolean/P.bigint/P.symbol:匹配对应基础类型。P.nullish:匹配null | undefined;P.nonNullable:匹配除二者外的任意值。
五、兜底:otherwise 与 exhaustive
import { match, P } from 'ts-pattern';
// exhaustive:要求覆盖全部可能,漏分支编译报错
const a = (n: 1 | 2) =>
match(n)
.with(1, () => 'one')
.with(2, () => 'two')
.exhaustive();
// otherwise:提供兜底,不强制覆盖每种情况
const b = (n: number) =>
match(n)
.with(1, () => 'one')
.otherwise(() => 'other'); // 等价 .with(P._, () => 'other').exhaustive()经验法则:能穷尽就用
.exhaustive()拿编译期安全;确实需要兜底(输入是开放类型如number/string)才用.otherwise()。
六、多模式:三选一走同一分支
import { match } from 'ts-pattern';
const kind = (tag: 'h1' | 'h2' | 'h3' | 'p') =>
match(tag)
// 在 handler 之前并列多个模式,命中任一即执行
.with('h1', 'h2', 'h3', () => '标题')
.with('p', () => '段落')
.exhaustive();注意区分:
.with('a', 'b', 'c', handler)是「三选一」;而.with(['a', 'b', 'c'], handler)会被解读为「匹配一个三元素元组」,语义完全不同。
掌握基本结构后,进入 指南 · 基础:P.array / P.union / P.when / P.select 等模式语言,与类型收窄的细节。