关于 conditional-types-over-overloads
这项 TypeScript 技能指导在相关输入对应不同返回类型时优先使用条件类型,而非重载。内容涵盖联合类型分配、实现方式,以及仍适合使用不同重载的情况。
主要功能
- 01用条件类型表达相关返回类型
- 02处理联合类型输入
- 03分析重载的取舍
- 04提供实现指导
适用场景
- 01设计精确的泛型函数签名
- 02无需增加重载即可处理联合输入
- 03审查重载过多的声明
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill conditional-types-over-overloads安装后在 Claude Code 中输入/conditional-types-over-overloads确认已加载。
详细介绍
优先使用条件类型,而不是重载签名
概述
当函数的返回类型取决于输入类型时,你可能会考虑使用重载签名。不过,条件类型通常是更好的解决方案。与分别进行检查的重载不同,条件类型会分布到联合类型上,并且可以作为单个表达式进行分析。因此,它们在处理联合输入时更强大,类型声明也更易于维护。
了解何时使用条件类型而非重载,是编写灵活、正确的类型签名的关键。
何时使用此技能
- 函数返回类型取决于输入类型
- 函数接受联合类型
- 正在考虑使用多个重载签名
- 需要根据输入获得精确的返回类型
- 正在构建转换类型的类型工具
铁律
优先使用条件类型,而不是重载签名。条件类型会分布到联合类型上,并提供更精确、易维护的类型声明。
检测
留意以下模式:
// RED FLAGS - Overloads that should be conditionals
declare function process(x: string): string;
declare function process(x: number): number;
declare function process(x: boolean): boolean;
// Missing union case!
// Or: Multiple overloads for what should be one conditional type
declare function transform(input: A): X;
declare function transform(input: B): Y;
declare function transform(input: C): Z;
重载的问题
重载会分别进行检查,且无法妥善处理联合类型:
// Overload approach
declare function double(x: number): number;
declare function double(x: string): string;
// Works for individual types
const n = double(12); // number
const s = double('x'); // string
// FAILS for unions
function f(x: string | number) {
return double(x);
// ~~~~~~~~~
// Error: No overload matches this call
}
条件类型解决方案
条件类型会自动分布到联合类型上:
// Conditional type approach
declare function double<T extends string | number>(
x: T
): T extends string ? string : number;
// Works for individual types
const n = double(12); // number
const s = double('x'); // string
// ALSO works for unions!
function f(x: string | number) {
return double(x); // string | number - correct!
}
分布式条件类型的工作方式
当 T 为 string | number 时,TypeScript 会进行如下计算:
(string | number) extends string ? string : number
→ (string extends string ? string : number) |
(number extends string ? string : number)
→ string | number
实际示例
// Event handler with different payloads
type EventMap = {
click: { x: number; y: number };
keypress: { key: string; code: string };
load: { timestamp: number };
};
// BAD: Multiple overloads needed
declare function onEvent(
type: 'click',
handler: (payload: { x: number; y: number }) => void
): void;
declare function onEvent(
type: 'keypress',
handler: (payload: { key: string; code: string }) => void
): void;
// ... more overloads for each event type
// GOOD: Single conditional type
declare function onEvent<T extends keyof EventMap>(
type: T,
handler: (payload: EventMap[T]) => void
): void;
// Usage
onEvent('click', (e) => {
console.log(e.x, e.y); // e is { x: number; y: number }
});
onEvent('keypress', (e) => {
console.log(e.key); // e is { key: string; code: string }
});
实现策略
实现具有条件返回类型的函数时,请使用单个重载:
// External signature with conditional type
declare function double<T extends string | number>(
x: T
): T extends string ? string : number;
// Implementation with simpler type
function double(x: string | number): string | number {
return typeof x === 'string' ? x + x : x + x;
}
适合使用重载的情况
以下情况使用重载可能仍然更清晰:
- 函数的行为相当于两个完全不同的函数
- 联合类型的情况不太可能出现
- 参数数量或形状不同
// Node's readFile - distinct use cases
// Using callbacks vs Promises are truly different patterns
readFile(path, callback); // callback version
readFile(path, options); // Promise version
// Better as separate functions or clear overloads
抗压原则
当有人以简化为由要求使用重载时:
- 测试联合类型情况:函数是否可能接收联合类型输入?
- 考虑分布特性:你是否希望类型分布到联合类型上?
- 检查可维护性:以后是否需要添加更多重载?
- 使用单个重载:实现时,在内部使用更简单的类型
危险信号
| 反模式 | 问题所在 |
|---|---|
| 为相关类型编写许多重载 | 难以维护,且无法涵盖联合类型情况 |
| 本应分布的重载 | 联合类型输入无法通过类型检查 |
| 复制粘贴重载 | 违反 DRY 原则 |
| 没有联合类型重载 | 迫使用户使用类型断言 |
常见的自我辩解
“重载更易读”
事实:单个条件类型通常比 5 个以上的重载更简单。复杂之处在于签名的数量,而非条件表达式。
“我不需要处理联合类型”
事实:联合类型在 TypeScript 中很常见。用户会向你的函数传入联合类型,而重载无法处理这种情况。
“需要时我会添加联合类型重载”
事实:条件类型会自动处理联合类型。手动添加联合类型重载容易出错,也会使代码冗长。
快速参考
| 方案 | 处理联合类型 | 可维护性 | 适用情况 |
|---|---|---|---|
| 重载 | 否(手动处理) | 差(签名众多) | 函数确实代表不同操作 |
| 条件类型 | 是(自动处理) | 好(单个签名) | 相关的返回类型 |
| 联合返回类型 | 是 | 简单 | 不需要精确类型 |
总结
与重载相比,条件类型会分布到联合类型上,并能提供更正确、易维护的类型声明。只有当函数代表完全不同的操作时,才使用重载。
参考
- Dan Vanderkam 所著的《Effective TypeScript》第 2 版
- 第 52 条:优先使用条件类型,而不是重载签名
评论
支持 Markdown