Skip to content

入门

本篇讲 ts-pattern 的基础用法match/with/exhaustive/otherwise、字面量与对象模式、最常用的 P.* 通配模式。版本基线 ts-pattern 5.9.0。对比对象:原生 if/elseswitch

速查

  • 安装: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」。三个关键点:

  1. 替代条件语句:用一条 match().with().exhaustive() 表达式表达原本要写一堆 if/else/switch 的分支逻辑。
  2. 类型安全:每个分支命中后,handler 拿到的值被精确收窄到对应类型;末尾 .exhaustive() 在编译期检查是否漏分支。
  3. 求值表达式match(...) 不是语句而是表达式,整体有返回值,可直接 const x = match(...)...

边界提醒:isMatching(pattern, unknownValue) 可以做布尔式结构校验与类型收窄,但 ts-pattern 不提供结构化错误、转换或默认值,不等同于完整 schema 库。复杂入口通常先由 Zod/Valibot 解析,再交给 ts-pattern 做穷尽分发。

二、第一个匹配表达式

ts
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 逐属性递归匹配——输入只要包含模式列出的键、且值满足子模式即命中(输入可有额外属性):

ts
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 字段。

四、常用通配模式

ts
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 | undefinedP.nonNullable:匹配除二者外的任意值。

五、兜底:otherwise 与 exhaustive

ts
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()

六、多模式:三选一走同一分支

ts
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 等模式语言,与类型收窄的细节。