avoid-inferable-annotations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAvoid Cluttering Your Code with Inferable Types
避免用可推断的类型注解让代码杂乱不堪
Overview
概述
Don't write type annotations when TypeScript can infer the same type.
Redundant type annotations add noise, increase maintenance burden, and can actually hide bugs. Let TypeScript do its job.
当TypeScript可以推断出相同类型时,请勿编写类型注解。
冗余的类型注解会增加代码噪音、提升维护负担,甚至可能隐藏bug。让TypeScript完成它的本职工作。
When to Use This Skill
何时运用此技巧
- Declaring variables with obvious types
- Destructuring assignments
- Writing function implementations
- Reviewing code with verbose annotations
- Refactoring type-heavy code
- 声明类型明显的变量时
- 解构赋值时
- 编写函数实现时
- 审查包含冗长注解的代码时
- 重构类型注解过多的代码时
The Iron Rule
铁则
NEVER annotate types that TypeScript can correctly infer.Exceptions allowed for:
- Object literals (to enable excess property checking)
- Public API function signatures
- When inference gives the wrong type
永远不要为TypeScript能正确推断的类型添加注解。允许例外的场景:
- 对象字面量(启用多余属性检查)
- 公开API的函数签名
- 类型推断结果错误时
Detection: The "Redundant Annotation Smell"
检测:“冗余注解异味”
Hover over a variable. If the inferred type matches your annotation, remove it.
typescript
// ❌ 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];将鼠标悬停在变量上。如果推断的类型与你添加的注解一致,就移除注解。
typescript
// ❌ 违规:冗余注解
let x: number = 12;
const name: string = 'Alice';
const numbers: number[] = [1, 2, 3];
// ✅ 正确:让TypeScript自行推断
let x = 12;
const name = 'Alice';
const numbers = [1, 2, 3];Complex Objects: Still Inferred
复杂对象:仍可被推断
typescript
// ❌ 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!typescript
// ❌ 冗长:请勿这样做
const person: {
name: string;
born: {
where: string;
when: string;
};
} = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// ✅ 简洁:只需编写值即可
const person = {
name: 'Sojourner Truth',
born: {
where: 'Swartekill, NY',
when: 'c.1797',
},
};
// TypeScript会推断出完全相同的类型!Function Parameters: Annotation Required
函数参数:必须添加注解
typescript
// 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
}typescript
// 参数需要类型(TypeScript无法推断)
function greet(name: string) {
return `Hello, ${name}`;
}
// 返回类型会被推断无需添加注解
function square(x: number) {
return x * x; // TypeScript推断为number类型
}Callback Parameters: Often Inferred
回调参数:通常可被推断
typescript
// ❌ 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
});typescript
// ❌ 冗长:不必要的参数类型
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('OK');
});
// ✅ 简洁:让上下文提供类型
app.get('/health', (request, response) => {
response.send('OK'); // 类型会从上下文推断得出
});When TO Annotate: Object Literals
何时需要添加注解:对象字面量
typescript
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 incompatibletypescript
interface Product {
id: string;
name: string;
price: number;
}
// ✅ 添加注解:启用多余属性检查
const elmo: Product = {
id: '123',
name: 'Tickle Me Elmo',
price: 28.99,
inStock: true, // 错误!'inStock' 不存在于类型 'Product' 中
};
// ❌ 不添加注解:错误会出现在远离定义的位置
const furby = {
id: 456, // 类型错误!但此处不会报错...
name: 'Furby',
price: 35,
};
logProduct(furby); // 错误出现在这里,远离出错点
// ~~~~~ 属性'id'的类型不兼容When TO Annotate: Function Return Types
何时需要添加注解:函数返回类型
Consider explicit return types for:
考虑为以下函数添加显式返回类型:
1. Functions with Multiple Returns
1. 存在多个返回分支的函数
typescript
// 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());
}typescript
// 没有注解时,容易忽略不一致性
function getQuote(ticker: string) {
if (cache[ticker]) {
return cache[ticker]; // 返回number类型
}
return fetch(`/api/${ticker}`).then(r => r.json()); // 返回Promise<any>类型
}
// 推断类型:number | Promise<any> —— 这大概率不是你想要的!
// 添加注解后,TypeScript会捕获bug
function getQuote(ticker: string): Promise<number> {
if (cache[ticker]) {
return cache[ticker]; // 错误:类型'number'无法赋值给类型'Promise<number>'
}
return fetch(`/api/${ticker}`).then(r => r.json());
}2. Public API Functions
2. 公开API函数
typescript
// For library functions, explicit types are documentation
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}typescript
// 对于库函数,显式类型就是文档
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}3. Named Return Types
3. 指定命名返回类型
typescript
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 };
}typescript
interface Vector2D { x: number; y: number; }
// 没有注解:类型为 { x: number; y: number; }
function add(a: Vector2D, b: Vector2D) {
return { x: a.x + b.x, y: a.y + b.y };
}
// 有注解:类型为Vector2D(更清晰,具备文档性)
function add(a: Vector2D, b: Vector2D): Vector2D {
return { x: a.x + b.x, y: a.y + b.y };
}Destructuring: Especially Bad
解构赋值:冗余注解的重灾区
typescript
// ❌ 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);
}typescript
// ❌ 糟糕:带类型的解构赋值非常丑陋
function logProduct(product: Product) {
const {id, name, price}: {id: string; name: string; price: number} = product;
console.log(id, name, price);
}
// ✅ 简洁:直接解构即可
function logProduct(product: Product) {
const {id, name, price} = product;
console.log(id, name, price);
}Pressure Resistance Protocol
应对质疑的方案
1. "Explicit Is Better"
1. “显式写法更好”
Pressure: "I want all types visible in the code"
Response: Redundant annotations add noise. Use your editor's hover feature to see types.
Action: Remove redundant annotations. Trust the inference.
质疑:“我希望所有类型都在代码中可见”
**回应:**冗余注解会增加代码噪音。使用编辑器的悬停功能即可查看类型。
**行动:**移除冗余注解,信任TypeScript的推断能力。
2. "What If Inference Changes?"
2. “如果推断结果变了怎么办?”
Pressure: "If I change the value, the type might change"
Response: That's often what you want! Your types evolve with your code.
Action: Add annotations only where you need to constrain the type.
质疑:“如果我修改了值,类型可能会改变”
**回应:**这往往是你想要的结果!类型会随代码一起演进。
**行动:**仅在需要约束类型的场景添加注解。
3. "New Developers Won't Know"
3. “新开发者看不懂”
Pressure: "Junior devs need to see the types"
Response: Modern editors show inferred types. Redundant annotations aren't teaching.
Action: Rely on editor tooling. Write meaningful type names, not verbose annotations.
质疑:“初级开发者需要看到类型”
**回应:**现代编辑器会显示推断出的类型。冗余注解并非有效的教学方式。
**行动:**依赖编辑器工具。编写有意义的类型名称,而非冗长的注解。
Red Flags - STOP and Reconsider
危险信号——立即停止并重新考虑
- on a variable initialized with a number literal
: number - on a variable initialized with a string literal
: string - Types on destructured variables
- Parameter types in callbacks where context provides them
- Same type appearing in both annotation and value
- 用字面量初始化的变量上标注
: number - 用字符串字面量初始化的变量上标注
: string - 为解构后的变量添加类型
- 在上下文已提供类型的回调参数上添加类型
- 注解的类型与值的类型完全一致
Common Rationalizations (All Invalid)
常见的不合理借口(全不成立)
| Excuse | Reality |
|---|---|
| "It's more readable" | Extra noise hurts readability. |
| "What if inference is wrong?" | Then add an annotation for that case. |
| "Code reviews need types" | Reviewers can hover in modern tools. |
| "Consistency" | Consistent non-redundancy is better. |
| 借口 | 实际情况 |
|---|---|
| “这样可读性更高” | 多余的噪音反而会损害可读性。 |
| “如果推断出错了怎么办?” | 那就在该场景下添加注解即可。 |
| “代码评审需要看到类型” | 评审者可通过现代工具悬停查看。 |
| “为了一致性” | 保持无冗余的一致性才是更好的选择。 |
Quick Reference
速查表
| Situation | Annotate? |
|---|---|
| Variable with literal value | No |
| Object literal for known type | Yes (excess checking) |
| Destructured variables | No |
| Function parameters | Yes |
| Function return (simple) | Usually no |
| Function return (complex/public) | Yes |
| Callback parameters | Usually no |
| 场景 | 是否需要注解? |
|---|---|
| 字面量初始化的变量 | 否 |
| 用于已知类型的对象字面量 | 是(启用多余属性检查) |
| 解构后的变量 | 否 |
| 函数参数 | 是 |
| 简单函数的返回值 | 通常不需要 |
| 复杂/公开函数的返回值 | 是 |
| 回调参数 | 通常不需要 |
The Bottom Line
核心结论
TypeScript is good at inferring types. Let it.
Annotate function parameters and public APIs. Annotate object literals to get excess property checking. For everything else, let TypeScript infer the types and keep your code clean.
TypeScript擅长推断类型,让它去做就好。
为函数参数和公开API添加注解。为对象字面量添加注解以启用多余属性检查。其他所有场景,让TypeScript自行推断类型,保持代码整洁。
Reference
参考资料
Based on "Effective TypeScript" by Dan Vanderkam, Item 18: Avoid Cluttering Your Code with Inferable Types.
基于Dan Vanderkam所著《Effective TypeScript》第18条:避免用可推断的类型注解让代码杂乱不堪。