关于 consistent-aliases
介绍如何一致地使用别名,使 TypeScript 控制流收窄符合预期。指南说明别名与源属性会被分别跟踪,并提供解构、局部变量和 readonly 参数等建议。
主要功能
- 01一致使用别名或源属性
- 02通过解构保持命名一致
- 03函数调用可能改值时优先使用局部变量
- 04用 readonly 参数保护类型收窄
- 05为 Map.get 和回退值采用安全写法
适用场景
- 01排查意外的 TypeScript 类型收窄错误
- 02避免属性检查后出现 undefined
- 03审查混用别名与属性访问的代码
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill consistent-aliases安装后在 Claude Code 中输入/consistent-aliases确认已加载。
详细介绍
始终一致地使用别名
概述
如果创建了别名,就要始终一致地使用它。
将对象属性赋给变量时,就创建了一个别名。TypeScript 会分别跟踪它们,因此缩窄其中一个不会缩窄另一个。选定一个并始终使用它。
何时使用此技能
- 类型缩窄没有按预期生效
- 同时使用变量及其源属性
- 属性检查没有影响别名变量
- 函数调用后,类型细化“丢失”
铁律
One alias OR the original - never mix them in the same scope.
Prefer destructuring for consistent naming.
请记住:
- 别名和原始变量会被分别跟踪
- 缩窄其中一个不会缩窄另一个
- 函数调用可能会使属性类型细化失效
- 局部变量比对象属性更安全
检测:别名会破坏类型缩窄
interface Polygon {
exterior: Coordinate[];
bbox?: BoundingBox;
}
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox; // Created an alias
if (polygon.bbox) { // Narrows polygon.bbox
if (pt.x < box.x[0]) { // box is still possibly undefined!
// ~~~
// 'box' is possibly 'undefined'
}
}
}
对 polygon.bbox 的检查不会缩窄 box。
黄金法则:始终一致地使用别名
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox;
if (box) { // Check the alias
if (pt.x < box.x[0]) { // Use the alias
// OK - box is narrowed here
}
}
}
现在,检查和使用都引用同一个变量。
最佳实践:使用解构
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const { bbox } = polygon; // Same name as property
if (bbox) {
const { x, y } = bbox; // Continue destructuring
if (pt.x < x[0] || pt.x > x[1] || pt.y < y[0] || pt.y > y[1]) {
return false;
}
}
return true;
}
优点:
- 命名一致(不会混淆
box和bbox) - 更简洁
- 与 TypeScript 的控制流分析配合良好
别名也会影响运行时
别名会影响运行时行为:
const { bbox } = polygon;
if (!bbox) {
calculatePolygonBbox(polygon); // Fills in polygon.bbox
// Now polygon.bbox exists, but bbox is still undefined!
}
别名和原始变量在运行时可能会出现不一致。
函数调用与类型细化
TypeScript 对函数调用采取务实的处理方式:
function expandPolygon(p: Polygon) { /* ... */ }
if (polygon.bbox) {
polygon.bbox // BoundingBox (narrowed)
expandPolygon(polygon);
polygon.bbox // Still BoundingBox (TypeScript trusts you)
// But the function might have set it to undefined!
}
TypeScript 假设函数不会使类型细化失效。这通常没问题,但也可能出错。
更安全的做法:使用局部变量
if (polygon.bbox) {
const bbox = polygon.bbox; // Capture it locally
expandPolygon(polygon);
bbox // Still BoundingBox - local variable is safe
}
函数调用无法修改局部变量。
使用 Readonly 提高安全性
function safeExpand(p: Readonly<Polygon>) {
// Can't modify p.bbox
}
if (polygon.bbox) {
safeExpand(polygon);
polygon.bbox // Guaranteed still BoundingBox
}
Readonly 参数可以避免对可变性的顾虑。
常见模式
使用空值合并,而非别名
// Instead of:
const name = person.nickname;
const displayName = name ? name : person.fullName;
// Use:
const displayName = person.nickname ?? person.fullName;
Map.get 模式
// TypeScript doesn't connect has() and get():
if (map.has(key)) {
const value = map.get(key); // Still T | undefined
}
// Better pattern:
const value = map.get(key);
if (value !== undefined) {
value // T (narrowed)
}
// Or with nullish coalescing:
const value = map.get(key) ?? defaultValue;
抗压应对准则
1. “我需要使用两个名称”
压力:“有时我用属性,有时我用变量”
**回应:**选一个。混用会破坏类型缩窄。
**行动:**使用解构,保持命名一致。
2. “函数不会修改它”
压力:“我知道 expandPolygon 不会碰 bbox”
**回应:**TypeScript 无法确定这一点。用 Readonly 明确说明。
**行动:**使用局部变量或 readonly 参数来确保安全。
危险信号——停下来重新考虑
- 根据属性创建变量后,又在条件中使用该属性
if检查后仍出现“可能是 undefined”的类型错误- 在同一代码块中混用属性访问和变量
- 依赖函数调用后的类型细化
常见的自我开脱(都不成立)
| 借口 | 事实 |
|---|---|
| “别名就是同一个东西” | TypeScript 会分别跟踪它们 |
| “我已经检查过这个属性了” | 检查不会缩窄别名 |
| “函数不会修改它” | TypeScript 无法验证这一点 |
快速参考
// DON'T: Mix alias and original
const box = polygon.bbox;
if (polygon.bbox) { // Checks original
box.x; // Uses alias - still possibly undefined!
}
// DO: Use alias consistently
const box = polygon.bbox;
if (box) { // Checks alias
box.x; // Uses alias - narrowed correctly
}
// DO: Use destructuring
const { bbox } = polygon;
if (bbox) {
const { x, y } = bbox;
}
要点
选定一个名称,并始终一致地使用它。
为属性创建别名时,TypeScript 会分别跟踪别名和原始变量。缩窄其中一个不会缩窄另一个。使用解构来保持命名一致;如果函数调用可能使类型细化失效,则优先使用局部变量。
参考资料
基于 Dan Vanderkam 所著的《Effective TypeScript》第 23 条:始终一致地使用别名。
评论
支持 Markdown