告别 if/else 和 switch:模式匹配在 TypeScript 中的新范式

在现代 TypeScript 开发中,处理复杂的数据结构和状态流是日常开发的重要组成部分。然而,传统的 if/elseswitch 语句往往使代码变得冗长、脆弱且难以维护。ts-pattern 库将强大的模式匹配概念引入 TypeScript 生态系统,从根本上改变了我们处理复杂逻辑的方式,不仅解决了传统控制流语句的痛点,还为代码带来了前所未有的类型安全性和可读性。

传统方案的核心问题

1. 冗长且类型不安全的 if/else

在处理**可辨识联合类型(Discriminated Union Types)**时,开发者通常需要编写冗长的 if/else 判断链来进行类型区分。这种方法存在严重的类型安全隐患:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type ApiResponse = 
| { type: 'success'; data: any }
| { type: 'error'; message: string }
| { type: 'loading' }
| { type: 'empty' };

// 传统 if/else 方式的问题
function handleResponse(response: ApiResponse) {
if (response.type === 'success') {
return response.data;
} else if (response.type === 'error') {
throw new Error(response.message);
} else if (response.type === 'loading') {
return 'Loading...';
}
// 如果新增了 'empty' 类型但忘记处理,TypeScript 不会报错
// 这会在运行时导致 undefined 返回值
}

核心问题

  • 缺乏穷举检查TypeScript 无法保证所有联合类型分支都被处理
  • 维护困难:新增类型时容易遗漏对应的处理逻辑
  • 运行时风险:未处理的分支可能导致意外的运行时行为

2. switch 语句的结构性局限

switch 语句虽然在语法上比 if/else 更简洁,但其功能局限性显著:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// switch 只能进行简单的值匹配
function processData(data: unknown) {
switch (typeof data) {
case 'string':
return data.toUpperCase();
case 'number':
return data * 2;
default:
// 无法进一步判断对象的具体结构
return null;
}
}

// 无法处理复杂的结构匹配需求
type ComplexData =
| { kind: 'user'; profile: { name: string; age: number } }
| { kind: 'admin'; permissions: string[] };

// switch 无法优雅地处理嵌套结构的匹配

核心限制

  • 仅支持原始值匹配:无法处理对象结构、数组模式等复杂匹配
  • 结构检查困难:不能直接判断对象是否包含特定属性或符合特定结构
  • 类型收窄有限:在复杂数据结构中类型收窄效果不佳

3. unknown 类型处理的复杂性

处理来自 API 或外部数据源的 unknown 类型数据时,需要大量的类型守卫和嵌套检查(unknown 的详细介绍可查看:拥抱 unknown,告别 any - TypeScript 类型安全最佳实践):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 传统的 unknown 类型处理方式
function processApiResponse(response: unknown) {
if (typeof response === 'object' && response !== null) {
const obj = response as Record<string, unknown>;

if ('user' in obj && typeof obj.user === 'object' && obj.user !== null) {
const user = obj.user as Record<string, unknown>;

if ('name' in user && typeof user.name === 'string') {
// 经过多层检查后才能安全使用
console.log(`User: ${user.name}`);
return;
}
}

if ('error' in obj && typeof obj.error === 'string') {
console.error(`Error: ${obj.error}`);
return;
}
}

console.warn('Unknown response format');
}

核心痛点

  • 嵌套层次深:多层类型检查导致代码可读性差
  • 样板代码多:大量重复的类型守卫逻辑
  • 维护成本高:结构变更时需要修改多个检查点

ts-pattern 带来的革命性改进

1. 强制穷举检查与类型安全

ts-pattern 的核心优势在于其强制的**穷举检查(Exhaustiveness Checking)**机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { match } from 'ts-pattern';

type ApiResponse =
| { type: 'success'; data: any }
| { type: 'error'; message: string }
| { type: 'loading' }
| { type: 'empty' };

function handleResponse(response: ApiResponse) {
return match(response)
.with({ type: 'success' }, (res) => res.data)
.with({ type: 'error' }, (res) => {
throw new Error(res.message);
})
.with({ type: 'loading' }, () => 'Loading...')
.with({ type: 'empty' }, () => 'No data available')
.exhaustive(); // 强制穷举检查
}

// 如果新增了类型分支但未处理,TypeScript 会在编译时报错
type ExtendedResponse = ApiResponse | { type: 'timeout'; duration: number };

function handleExtendedResponse(response: ExtendedResponse) {
return match(response)
.with({ type: 'success' }, (res) => res.data)
.with({ type: 'error' }, (res) => {
throw new Error(res.message);
})
.with({ type: 'loading' }, () => 'Loading...')
.with({ type: 'empty' }, () => 'No data available')
// .with({ type: 'timeout' }, ...) // 遗漏这行会导致 TypeScript 编译错误
.exhaustive();
}

2. 丰富的模式匹配能力

ts-pattern 通过 P 模块提供了强大的模式守卫系统,支持多种匹配模式:

对象结构匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { match, P } from 'ts-pattern';

type UserAction =
| { type: 'login'; user: { id: number; name: string } }
| { type: 'logout' }
| { type: 'update'; user: { id: number; changes: Record<string, any> } };

function handleUserAction(action: UserAction) {
return match(action)
.with(
{ type: 'login', user: { name: P.string } },
(action) => `Welcome, ${action.user.name}!`
)
.with(
{ type: 'update', user: { id: P.number } },
(action) => `Updating user ${action.user.id}`
)
.with({ type: 'logout' }, () => 'Goodbye!')
.exhaustive();
}

数组模式匹配

1
2
3
4
5
6
7
8
function processArray(arr: unknown[]) {
return match(arr)
.with([], () => 'Empty array')
.with([P.string], (arr) => `Single string: ${arr[0]}`)
.with([P.string, P.number], (arr) => `String and number: ${arr[0]}, ${arr[1]}`)
.with(P.array(P.string), (arr) => `Array of strings: ${arr.join(', ')}`)
.otherwise(() => 'Other array pattern');
}

自定义守卫模式

1
2
3
4
5
6
7
8
function processNumber(value: number) {
return match(value)
.with(P.when(n => n > 100), (n) => `Large number: ${n}`)
.with(P.when(n => n > 0), (n) => `Positive number: ${n}`)
.with(P.when(n => n < 0), (n) => `Negative number: ${n}`)
.with(0, () => 'Zero')
.exhaustive();
}

3. 优雅的 unknown 类型处理

ts-pattern 将复杂的类型检查转化为声明式的模式声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { match, P } from 'ts-pattern';

function processApiResponse(response: unknown) {
return match(response)
.with(
{ user: { name: P.string, age: P.number } },
(res) => {
// res 自动收窄为 { user: { name: string; age: number } }
console.log(`User: ${res.user.name}, Age: ${res.user.age}`);
return 'user-processed';
}
)
.with(
{ users: P.array({ name: P.string }) },
(res) => {
// res 自动收窄为 { users: { name: string }[] }
console.log(`Users: ${res.users.map(u => u.name).join(', ')}`);
return 'users-processed';
}
)
.with(
{ error: P.string },
(res) => {
console.error(`API Error: ${res.error}`);
return 'error-handled';
}
)
.with(
{ status: P.union('loading', 'pending') },
() => {
console.log('Request in progress...');
return 'loading';
}
)
.otherwise(() => {
console.warn('Unknown response format');
return 'unknown';
});
}

4. 声明式编程范式

模式匹配将命令式的”如何检查”转变为声明式的”期望什么”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 传统命令式方法
function processState(state: AppState) {
if (state.loading) {
if (state.error) {
return showErrorWithLoading();
} else {
return showLoading();
}
} else {
if (state.error) {
return showError();
} else if (state.data) {
return showData(state.data);
} else {
return showEmpty();
}
}
}

// ts-pattern 声明式方法
function processState(state: AppState) {
return match(state)
.with({ loading: true, error: P.not(P.nullish) }, showErrorWithLoading)
.with({ loading: true }, showLoading)
.with({ error: P.not(P.nullish) }, showError)
.with({ data: P.not(P.nullish) }, (state) => showData(state.data))
.otherwise(showEmpty);
}

总结

ts-pattern 将函数式编程中成熟的模式匹配概念成功引入 TypeScript 生态系统,从根本上解决了传统控制流语句在类型安全、代码可读性和维护性方面的痛点。通过提供强制穷举检查、丰富的模式匹配能力和优雅的类型收窄机制,ts-patternTypeScript 开发者提供了一种更安全、更简洁、更具表达力的方式来处理复杂的数据结构和业务逻辑。