关于 avoid-unnecessary-type-params
面向审查泛型函数和类型的 TypeScript 开发者的技能。它说明多余的类型参数会增加复杂度、妨碍推断,并可能带来没有类型关系支撑的安全假象。
主要功能
- 01识别仅使用一次的类型参数
- 02解释泛型建立类型关系的规则
- 03建议对弱类型输入使用 unknown
- 04简化泛型函数签名
- 05检查泛型是否改善类型推断
适用场景
- 01审查泛型 API 签名
- 02排查类型推断不佳的问题
- 03减少代码中的多余泛型
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill avoid-unnecessary-type-params安装后在 Claude Code 中输入/avoid-unnecessary-type-params确认已加载。
详细介绍
避免不必要的类型参数
概述
“泛型黄金法则”指出,类型参数应在函数签名中出现两次或更多次。如果类型参数只出现一次,它就没有关联任何内容,很可能是不必要的。不必要的类型参数会制造类型安全的假象,还可能降低类型推断的效果。
本技能可帮助你识别并消除多余的类型参数,从而编写更简洁、更易维护的代码,让 TypeScript 能更有效地进行类型推断。
何时使用此技能
- 编写泛型函数或类型
- 检查类型签名是否清晰
- 类型参数(除声明处外)只出现一次
- 想添加泛型来增加“灵活性”
- 调试不理想的类型推断
铁律
类型参数必须出现两次或更多次,才能建立关联。如果类型参数只出现一次,请认真考虑是否真的需要它。
检测
留意以下模式:
// RED FLAGS - Unnecessary type parameters
function parseYAML<T>(input: string): T; // Return-only generic
type Fn = <T>(x: T) => void; // T only used once
function third<A, B, C>(a: A, b: B, c: C): C; // A and B appear once
function getLength<T extends Lengthy>(x: T): number; // T only in parameter
黄金法则
类型参数用于关联类型。如果类型参数只出现一次,它就没有关联任何内容:
// GOOD: T appears twice, relating input and output
function identity<T>(arg: T): T {
return arg;
}
// BAD: T only appears once (in return type)
function parseYAML<T>(input: string): T;
// Equivalent to: function parseYAML(input: string): any
// Usage shows the problem:
const data: User = parseYAML('...'); // Looks safe, but T can be anything
const bad: Banana = parseYAML('...'); // Also "valid" - no error!
仅用于返回值的泛型
仅用于返回值的泛型很危险,因为它们等同于没有显式 as 的类型断言:
// BAD: Return-only generic
declare function parseYAML<T>(input: string): T;
const user: User = parseYAML('...'); // No error, but no validation either
// GOOD: Return unknown, force explicit assertion
declare function parseYAML(input: string): unknown;
const user = parseYAML('...') as User; // Explicit about the assertion
// Or use validation:
const user = parseYAML('...');
if (isUser(user)) { // Type guard
// user is User here
}
简化不必要的泛型
// BEFORE: Unnecessary type parameters
function third<A, B, C>(a: A, b: B, c: C): C {
return c;
}
// AFTER: Only keep C which appears twice
function third<C>(a: unknown, b: unknown, c: C): C {
return c;
}
何时需要泛型
// GOOD: K appears twice, relating parameter to return type
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// K appears in: parameter type (key: K) and return type (T[K])
// This IS a good use of generics
类泛型
// GOOD: T appears multiple times, relating class properties
class Container<T> {
private items: T[] = [];
add(item: T): void { this.items.push(item); }
getAll(): T[] { return this.items; }
}
// BAD: T only used in one method
class Joiner<T extends string | number> {
join(els: T[]): string {
return els.map(String).join(',');
}
}
// Better: Move T to method or use concrete type
class Joiner {
join(els: (string | number)[]): string {
return els.map(String).join(',');
}
}
// Best: Just a function
function join(els: (string | number)[]): string {
return els.map(String).join(',');
}
抵御压力的准则
当有人施压要求添加“灵活”的泛型时:
- 统计出现次数:类型参数是否出现了两次或更多次?
- 检查关联关系:它是否关联了多个值的类型?
- 考虑
unknown:unknown是否同样适用? - 测试类型推断:泛型能改善还是损害类型推断?
危险信号
| 反模式 | 不好的原因 |
|---|---|
function f<T>(): T | 仅用于返回值,不具备类型安全性 |
type Fn = <T>(x: T) => void | T 未用于返回值,因此不必要 |
| 单次使用类型的泛型 | 增加复杂度,却没有带来好处 |
T extends X,其中 T 只出现一次 | 约束没有帮助 |
常见的自我辩解
“这样能让 API 更灵活”
事实:不必要的泛型会制造安全的假象。它们不会带来真正的灵活性,只会造成困惑。
“以后会用到”
事实:需要时再添加泛型,不要提前添加。之后随时可以添加。
“看起来更专业”
事实:简单、正确的类型比复杂且不必要的类型更专业。
快速参考
| 模式 | 结论 | 修正方式 |
|---|---|---|
function f<T>(x: T): T | ✓ 良好 | T 关联输入与输出 |
function f<T>(x: string): T | ✗ 不好 | 使用 unknown 或具体类型 |
function f<T>(x: T): void | ✗ 不好 | 使用 unknown 或 any |
class C<T> { method(x: T): T } | ✓ 良好 | T 关联了多处用法 |
class C<T> { method(x: T): void } | ✗ 不好 | 将 T 移到方法上,或移除 |
总结
泛型的第一条规则就是“不要用”。只有当类型参数能在类型之间建立关联时才添加它。如果类型参数只出现一次,就将其移除。这样能让代码更简洁,并改善类型推断。
参考资料
- Dan Vanderkam 著《Effective TypeScript》,第 2 版
- 条目 51:避免不必要的类型参数
评论
支持 Markdown