noUncheckedIndexedAccess 解决了什么类型安全问题?
简化版
noUncheckedIndexedAccess 会让通过索引访问数组或对象时,把结果类型自动加上 undefined,提醒你这个 key 或下标可能不存在。
它能发现很多越界访问、字典缺 key 的问题,但也会让代码里出现更多空值处理,适合对类型安全要求更高的项目逐步开启。
详细版
不开启时:
const list = ["a"];
const item = list[10]; // string
开启后:
const item = list[10]; // string | undefined
对象字典:
const map: Record<string, number> = {};
const count = map["missing"]; // number | undefined
这更符合运行时真实行为,因为访问不存在的属性会得到 undefined。
完整版教学
一、索引访问的运行时真相
JavaScript 访问不存在的数组下标或对象属性会返回 undefined。
const list = ["x"];
console.log(list[99]); // undefined
但默认 TypeScript 往往根据容器声明返回元素类型。
这个选项把“可能不存在”从运行时事实带回类型系统。
二、数组下标越界更容易暴露
function first(items: string[]) {
return items[0].toUpperCase();
}
开启后,items[0] 是 string | undefined。
如果数组可能为空,就必须处理:
function first(items: string[]) {
const item = items[0];
return item ? item.toUpperCase() : "";
}
三、字典对象缺 key 更真实
const scores: Record<string, number> = {};
const score = scores["Ada"];
不开启时看起来是 number,实际可能是 undefined。
开启后变成:
const score: number | undefined = scores["Ada"];
这能避免把缺失值直接参与计算。
四、和可选链、空值合并配合
const label = labels[id] ?? "未知";
或者先判断:
const value = cache[key];
if (value !== undefined) {
use(value);
}
如果 undefined 本身是合法值,就需要用 Object.hasOwn 判断 key 是否存在。
五、它不会改变明确属性访问
type User = {
name: string;
};
declare const user: User;
user.name; // string
这个选项主要影响索引访问,不会把所有属性都变成可选。
| 访问方式 | 可能变化 |
|---|---|
arr[i] | 加 undefined |
dict[key] | 加 undefined |
obj.name | 通常不变 |
六、迁移成本和策略
开启后,老项目可能出现大量报错。
建议:
- 新项目直接开启
- 老项目按目录逐步迁移
- 先处理数据边界层
- 对确实存在的值使用明确判断
- 少用非空断言批量压错
const item = list[index]!;
! 应该是最后手段,不是迁移策略。
七、常见误区与追问
- 误区:数组声明为 string[] 就保证任意下标都有 string。 越界访问仍然是 undefined。
- 误区:Record<string, T> 表示所有字符串 key 都真实存在。 运行时普通对象可以缺任意 key。
- 误区:开启后所有属性都会变可选。 主要影响索引访问。
- 误区:用
!全部压掉就完成迁移。 这会绕过选项带来的安全收益。 - 追问:如果 undefined 是合法值怎么办? 用 key 存在性判断区分缺 key 和值为 undefined。
- 追问:适合什么时候开启? 新项目或高可靠项目更适合,老项目可渐进开启。
八、加强记忆
记忆图:
container[index] -> value | undefined
because index may not exist
回答时强调它贴近 JavaScript 运行时真实行为,再讲数组越界和字典缺 key 两个场景,最后补迁移成本。