关于 avoid-inferable-annotations
面向 TypeScript 开发者的编码技能,指导移除编译器可推断的冗余类型标注,同时保留用于 API 表达或约束值的标注。
主要功能
- 01避免冗余变量类型标注
- 02利用上下文推断回调类型
- 03为公开 API 标注签名
- 04必要时为对象字面量标注类型
- 05识别类型推断相关异味
适用场景
- 01审查类型标注冗长的代码
- 02重构带类型的变量声明
- 03判断返回值是否需要标注
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill avoid-inferable-annotations安装后在 Claude Code 中输入/avoid-inferable-annotations确认已加载。
详细介绍
避免在代码中添加可推断类型的注解
概述
TypeScript 能推断出相同类型时,不要编写类型注解。
多余的类型注解会增加冗余,提高维护成本,甚至可能掩盖错误。让 TypeScript 做好它该做的事。
何时使用此技能
- 声明类型一目了然的变量
- 解构赋值
- 编写函数实现
- 检查注解冗长的代码
- 重构类型繁多的代码
铁律
NEVER annotate types that TypeScript can correctly infer.
以下情况除外:
- 对象字面量(用于启用多余属性检查)
- 公共 API 函数签名
- 推断结果不正确时
如何识别:“多余注解”的迹象
将鼠标悬停在变量上。如果推断出的类型与注解相同,就删除注解。
// ❌ VIOLATION: Redundant annotations
let x: number = 12;
const name: string = 'Alice';
const numbers: number[] = [1, 2, 3];
// ✅ CORRECT: Let TypeScript infer
let x = 12;
const name = 'Alice';
const numbers = [1, 2, 3];
复杂对象:仍可推断
// ❌ VERBOSE: Don't do this
const person: {
name: string;
born: {
where: string;
when: string;
};
} = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// ✅ CONCISE: Just write the value
const person = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// TypeScript infers the exact same type!
函数参数:需要注解
// Parameters need types (TypeScript can't infer them)
function greet(name: string) {
return `Hello, ${name}`;
}
// Return type is inferred - no need to annotate
function square(x: number) {
return x * x; // TypeScript infers number
}
回调参数:通常可以推断
// ❌ VERBOSE: Unnecessary parameter types
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('OK');
});
// ✅ CONCISE: Let context provide the types
app.get('/health', (request, response) => {
response.send('OK'); // Types are inferred from context
});
何时需要添加注解:对象字面量
interface Product {
id: string;
name: string;
price: number;
}
// ✅ ANNOTATE: Enables excess property checking
const elmo: Product = {
id: '123',
name: 'Tickle Me Elmo',
price: 28.99,
inStock: true, // Error! 'inStock' does not exist in type 'Product'
};
// ❌ NO ANNOTATION: Error appears far from definition
const furby = {
id: 456, // Wrong type! But no error here...
name: 'Furby',
price: 35,
};
logProduct(furby); // Error appears here, far from the mistake
// ~~~~~ Types of property 'id' are incompatible
何时需要添加注解:函数返回类型
请考虑为以下情况显式声明返回类型:
1. 有多个返回值的函数
// Without annotation, easy to miss inconsistencies
function getQuote(ticker: string) {
if (cache[ticker]) {
return cache[ticker]; // Returns number
}
return fetch(`/api/${ticker}`).then(r => r.json()); // Returns Promise<any>
}
// Inferred: number | Promise<any> - probably not what you wanted!
// With annotation, TypeScript catches the bug
function getQuote(ticker: string): Promise<number> {
if (cache[ticker]) {
return cache[ticker]; // Error: Type 'number' is not assignable to 'Promise<number>'
}
return fetch(`/api/${ticker}`).then(r => r.json());
}
2. 公共 API 函数
// For library functions, explicit types are documentation
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
3. 命名返回类型
interface Vector2D { x: number; y: number; }
// Without annotation: { x: number; y: number; }
function add(a: Vector2D, b: Vector2D) {
return { x: a.x + b.x, y: a.y + b.y };
}
// With annotation: Vector2D (clearer, documented)
function add(a: Vector2D, b: Vector2D): Vector2D {
return { x: a.x + b.x, y: a.y + b.y };
}
解构:尤其不应添加注解
// ❌ TERRIBLE: Destructuring with types is ugly
function logProduct(product: Product) {
const {id, name, price}: {id: string; name: string; price: number} = product;
console.log(id, name, price);
}
// ✅ CLEAN: Just destructure
function logProduct(product: Product) {
const {id, name, price} = product;
console.log(id, name, price);
}
抵制压力的准则
1. “显式优于隐式”
压力:“我希望代码中能看到所有类型”
**回应:**多余的注解会增加冗余。使用编辑器的悬停功能查看类型。
**行动:**删除多余的注解。相信类型推断。
2. “如果推断结果变了怎么办?”
压力:“如果我修改值,类型可能也会改变”
**回应:**这通常正是你想要的!类型会随代码一起演变。
**行动:**只在需要限制类型时添加注解。
3. “新开发者会看不懂”
压力:“初级开发者需要看到类型”
**回应:**现代编辑器会显示推断出的类型。多余的注解并不能帮助他们学习。
**行动:**依靠编辑器工具。使用有意义的类型名称,而不是冗长的注解。
警示信号——停下来重新考虑
- 用数字字面量初始化的变量却标注了
: number - 用字符串字面量初始化的变量却标注了
: string - 为解构出的变量添加类型
- 回调的上下文已提供参数类型,却仍显式标注
- 注解和赋值的值中出现相同类型
常见的自我辩解(都不成立)
| 借口 | 实际情况 |
|---|---|
| “这样更易读” | 额外的冗余会降低可读性。 |
| “如果推断错了怎么办?” | 那么只针对这种情况添加注解。 |
| “代码审查需要类型” | 审查者可以在现代工具中悬停查看类型。 |
| “保持一致” | 始终避免冗余更好。 |
快速参考
| 情况 | 是否添加注解? |
|---|---|
| 值为字面量的变量 | 否 |
| 用于已知类型的对象字面量 | 是(多余属性检查) |
| 解构出的变量 | 否 |
| 函数参数 | 是 |
| 函数返回值(简单情况) | 通常不需要 |
| 函数返回值(复杂情况/公共函数) | 是 |
| 回调参数 | 通常不需要 |
总结
TypeScript 擅长推断类型。就让它来做。
为函数参数和公共 API 添加注解。为对象字面量添加注解,以启用多余属性检查。其他情况下,让 TypeScript 推断类型,保持代码简洁。
参考资料
基于 Dan Vanderkam 的《Effective TypeScript》第 18 条:避免在代码中添加可推断类型的注解。
评论
支持 Markdown