mcpField
C
conditional-types-over-overloads

conditional-types-over-overloads

github.com/marius-townhouse开发644 次安装38 周前更新
代码仓库

关于 conditional-types-over-overloads

这项 TypeScript 技能指导在相关输入对应不同返回类型时优先使用条件类型,而非重载。内容涵盖联合类型分配、实现方式,以及仍适合使用不同重载的情况。

主要功能

  1. 01用条件类型表达相关返回类型
  2. 02处理联合类型输入
  3. 03分析重载的取舍
  4. 04提供实现指导

适用场景

  1. 01设计精确的泛型函数签名
  2. 02无需增加重载即可处理联合输入
  3. 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

抗压原则

当有人以简化为由要求使用重载时:

  1. 测试联合类型情况:函数是否可能接收联合类型输入?
  2. 考虑分布特性:你是否希望类型分布到联合类型上?
  3. 检查可维护性:以后是否需要添加更多重载?
  4. 使用单个重载:实现时,在内部使用更简单的类型

危险信号

反模式问题所在
为相关类型编写许多重载难以维护,且无法涵盖联合类型情况
本应分布的重载联合类型输入无法通过类型检查
复制粘贴重载违反 DRY 原则
没有联合类型重载迫使用户使用类型断言

常见的自我辩解

“重载更易读”

事实:单个条件类型通常比 5 个以上的重载更简单。复杂之处在于签名的数量,而非条件表达式。

“我不需要处理联合类型”

事实:联合类型在 TypeScript 中很常见。用户会向你的函数传入联合类型,而重载无法处理这种情况。

“需要时我会添加联合类型重载”

事实:条件类型会自动处理联合类型。手动添加联合类型重载容易出错,也会使代码冗长。

快速参考

方案处理联合类型可维护性适用情况
重载否(手动处理)差(签名众多)函数确实代表不同操作
条件类型是(自动处理)好(单个签名)相关的返回类型
联合返回类型是简单不需要精确类型

总结

与重载相比,条件类型会分布到联合类型上,并能提供更正确、易维护的类型声明。只有当函数代表完全不同的操作时,才使用重载。

参考

  • Dan Vanderkam 所著的《Effective TypeScript》第 2 版
  • 第 52 条:优先使用条件类型,而不是重载签名

评论

支持 Markdown