指南 · 进阶
版本基线 ts-pattern 5.x。把模式匹配用进真实场景:
P.select数据提取、带守卫的.with、判别联合 / 状态机、isMatching类型守卫、变长元组、链式断言。
一、P.select:从结构里提取值
P.select() 让你在匹配的同时挑出深层的值,免去手动解构。匿名 select 把选中值作为 handler 的第一个参数:
import { match, P } from 'ts-pattern';
type Input = { type: 'post'; user: { name: string } };
const author = (input: Input) =>
match(input)
.with({ type: 'post', user: { name: P.select() } }, (name) => name) // name: string
.otherwise(() => 'anonymous');命名 select 用于一个分支里提取多处值,汇成一个对象传入:
match(input)
.with(
{ type: 'post', user: { name: P.select('name') }, content: P.select('body') },
({ name, body }) => `${name} 写道:${body}`
)
.otherwise(() => '');匿名 select 还能拿到第二个参数(完整输入);命名 select 则统一走「选择对象」。带子模式的
P.select('age', P.number.gt(18))会先约束条件、命中后再提取该值。
二、带守卫的 .with:结构 + 额外条件
在模式与 handler 之间插入守卫函数,二者皆满足才命中:
import { match, P } from 'ts-pattern';
type State = { status: 'loading'; startTime: number };
type Event = { type: 'cancel' };
match([state, event] as [State, Event])
.with(
[{ status: 'loading' }, { type: 'cancel' }], // 先按结构匹配元组
([s]) => s.startTime + 2000 < Date.now(), // 再用守卫判断是否超时
() => ({ status: 'idle' })
)
.otherwise(() => state);数组模式
[patternA, patternB]按位置匹配元组,是状态机里「某状态下收到某事件」的经典写法。
三、判别联合与状态机
判别联合(靠 status/type 这种公共字段区分)是 ts-pattern 的主场。每个分支自动收窄,配 .exhaustive() 保证全覆盖:
import { match, P } from 'ts-pattern';
type Data =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; error: Error };
const view = (s: Data) =>
match(s)
.with({ status: 'idle' }, () => '空闲')
.with({ status: 'loading' }, () => '加载中…')
// 用 select 直接取出 data
.with({ status: 'success', data: P.select() }, (data) => `${data.length} 条`)
.with({ status: 'error' }, (e) => `错误:${e.error.message}`)
.exhaustive(); // 新增一个 status 而忘了处理 → 编译报错这正是相对
switch的最大价值:新增联合成员时,所有漏处理的 match 立刻编译报错,把「漏 case」从运行时 bug 变成编译错误。
四、isMatching:把模式当类型守卫
match 用于选分支取值,isMatching 则返回布尔、用于 if/filter 里收窄类型:
import { isMatching, P } from 'ts-pattern';
// 柯里化:生成可复用的守卫函数
const isBlogPost = isMatching({ type: 'blogpost', title: P.string });
if (isBlogPost(value)) {
// value 被收窄为 { type: 'blogpost'; title: string }
console.log(value.title);
}
// 两参用法:直接判断
const posts = items.filter((it) => isMatching({ type: 'post' }, it));五、变长元组
用展开语法把固定元素与 ...P.array(sub) 组合,匹配「头/尾固定、中间任意多个」的数组:
import { match, P } from 'ts-pattern';
match(tokens)
.with([P.string, ...P.array()], (t) => t) // 首个是字符串,其后任意
.with(['print', ...P.array(P.string)], (t) => t) // 'print' 开头,后接任意多个字符串
.with([...P.array(P.string), 'end'], (t) => t) // 以 'end' 结尾
.with(['start', ...P.array(P.string), 'end'], (t) => t) // 首尾固定、中间任意多个字符串
.otherwise((t) => t);六、链式断言:少写 P.when
字符串/数字模式自带断言,常见约束无需手写 P.when:
import { match, P } from 'ts-pattern';
match(input)
.with(P.string.startsWith('http'), () => 'URL')
.with(P.string.regex(/^\d+$/), () => '纯数字串')
.with(P.number.between(1, 5), () => '1~5 的评分')
.with(P.number.int().positive(), () => '正整数')
.otherwise(() => '其它');数值断言(
int/positive/between…)只影响运行时是否命中;TypeScript 没有「正整数」类型,P.infer层面通常仍是number。
进入 指南 · 专家:exhaustive 穷尽性原理与 NonExhaustiveError、P.infer 模式即类型、returnType 与输出推导、与 Zod 的协作、性能与边界。