关于 prefer-type-annotations
prefer-type-annotations 是指导 TypeScript 开发者优先使用类型注解而非类型断言的技能。它强调注解会检查值是否符合类型,并提供识别断言风险及少数适用情形的规则。
主要功能
- 01优先用注解验证值的类型
- 02识别并重新检查 as 断言
- 03为函数返回值添加类型注解
- 04了解 DOM 等断言适用情形
- 05通过运行时检查缩窄类型
适用场景
- 01声明变量或对象字面量类型
- 02定义函数返回类型
- 03排查不兼容类型错误
- 04评估是否应保留类型断言
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill prefer-type-annotations安装后在 Claude Code 中输入/prefer-type-annotations确认已加载。
详细介绍
优先使用类型注解,而非类型断言
概述
类型注解(: Type)会验证值是否符合类型。类型断言(as Type)则是告诉 TypeScript:相信你。
注解会检查你的代码。断言会绕过检查。可以选择时,优先使用注解。
何时使用此技能
- 为变量赋值
- 定义函数返回类型
- 使用对象字面量
- 想用
as Type修复错误时 - 遇到“不可赋值”错误时
铁律
NEVER use type assertions when type annotations would work.
没有例外:
- 不能因为“这显然没问题”
- 不能因为“我比 TypeScript 更清楚”
- 不能因为“断言更简单”
识别:“as”异味
看到 as SomeType?问问自己:“能否改用类型注解?”
// ❌ VIOLATION: Type assertion bypasses checking
const alice = {} as Person; // No error! But alice has no properties
// ✅ CORRECT: Type annotation verifies the value
const alice: Person = {};
// ~~~~~ Property 'name' is missing in type '{}' but required in type 'Person'
关键区别
类型注解(安全)
interface Person { name: string }
// TypeScript CHECKS that the value matches the type
const bob: Person = { name: 'Bob' }; // OK
const bad: Person = {}; // Error: missing 'name'
const extra: Person = {
name: 'Carol',
age: 30 // Error: 'age' does not exist on type 'Person'
};
类型断言(不安全)
// TypeScript TRUSTS you that the value matches the type
const bob = {} as Person; // No error, but wrong!
const bad = { foo: 1 } as Person; // No error, but wrong!
箭头函数:为返回值添加注解
// ❌ VIOLATION: Assertion hides errors
const people = ['alice', 'bob'].map(name => ({name} as Person));
// Even worse: completely wrong values slip through
const people = ['alice', 'bob'].map(name => ({} as Person)); // No error!
// ✅ CORRECT: Annotate the return type
const people = ['alice', 'bob'].map((name): Person => ({name})); // OK
const people = ['alice', 'bob'].map((name): Person => ({}));
// ~~ Error: missing 'name'
何时适合使用断言
只有在你确实比 TypeScript 知道得更多时,类型断言才有意义:
1. DOM 元素
// You know #myButton exists and is a button
const button = document.querySelector('#myButton') as HTMLButtonElement;
// Better: include a comment explaining why
const button = document.querySelector('#myButton') as HTMLButtonElement;
// This button is created in index.html and always exists
2. 运行时检查之后
const el = document.getElementById('foo');
if (el) {
el.innerHTML = 'Hello'; // TypeScript knows el is not null
}
// Or with non-null assertion (use sparingly!)
const el = document.getElementById('foo')!;
抵御压力的守则
1. “这个错误不对”
压力:“TypeScript 报错了,但我的代码是正确的”
**回应:**TypeScript 通常是对的。仔细阅读错误信息。
行动:
- 检查类型定义是否符合你的意图
- 检查值是否确实符合该类型
- 只有在能解释 TypeScript 为何出错时,才使用断言
2. “断言更简单”
压力:“到处添加注解太冗长了”
**回应:**注解能捕获错误,断言会掩盖错误。安全 > 简洁。
**行动:**添加注解。未来的你会感谢现在的你。
3. “我知道运行时的类型”
压力:“我在运行时检查过类型,所以断言是安全的”
**回应:**如果你在运行时检查过,TypeScript 应该能够缩窄类型范围。
**行动:**使用类型守卫或条件语句来缩窄类型,而不是使用断言。
危险信号——立即停止并重新考虑
- 代码中任何位置出现
as any - 创建对象后立刻使用
as Type - 连续使用多个断言(
x as A as B) - 使用断言修复“不可赋值”错误
- 没有充分理由就使用
!(非空断言)
常见的自我安慰(都不成立)
| 借口 | 事实 |
|---|---|
| “我知道它是 Person” | 那就用注解证明,而不是用断言。 |
| “TypeScript 错了” | TypeScript 发现了真实的不一致。查明原因。 |
| “只是一个断言” | 断言会蔓延。一个会带来更多断言。 |
| “类型限制太严格了” | 严格的类型能捕获错误。接受它们。 |
快速参考
| 场景 | 使用注解 | 使用断言 |
|---|---|---|
| 变量声明 | :Type | 从不 |
| 函数参数 | :Type | 从不 |
| 函数返回值 | :Type | 很少 |
| 对象字面量 | :Type | 从不 |
| 你确定类型的 DOM 元素 | - | as HTMLElement,并添加注释 |
| 空值检查之后 | - | 确定时使用 ! |
| 来自 API 的未知值 | - | 使用 unknown 验证之后 |
非空断言(!)
// Tells TypeScript: "Trust me, this isn't null"
const el = document.getElementById('foo')!;
// Prefer a runtime check:
const el = document.getElementById('foo');
if (!el) throw new Error('No element #foo');
// Now TypeScript knows el is not null
总之
类型注解是一份契约。类型断言则是你对 TypeScript 撒的谎。
使用注解来声明你的意图。只有当你掌握了 TypeScript 无法获得的信息时,才使用断言。务必添加注释,说明断言为何有效。
参考
基于 Dan Vanderkam 的《Effective TypeScript》第 9 条:优先使用类型注解,而非类型断言。
评论
支持 Markdown