strict 模式全解:把隐患挡在编译期

本节目标

"strict": true 其实是下面这些子选项的「总开关」:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,        // 不允许隐式 any
    "strictNullChecks": true,     // 禁止把 null/undefined 赋给非可空类型
    "strictFunctionTypes": true,  // 函数参数逆变检查更严
    "strictBindCallApply": true,  // bind/call/apply 也做类型检查
    "strictPropertyInitialization": true, // 类属性必须初始化
    "noImplicitThis": true,       // this 类型不明确时报错
    "alwaysStrict": true          // 输出 "use strict"
  }
}

其中最关键的是 strictNullChecks。关掉时,null 能赋给任何类型,于是 user.name 在 user 为 null 时也不报错——这是 JS 里 Cannot read 'x' of null 的根源。

// 开启 strictNullChecks 后
function getName(u: { name: string } | null): string {
  // return u.name // ❌ u 可能是 null,必须先判空
  return u ? u.name : '匿名'
}

// 渐进式迁移老项目:先不开总开关,单独开最关键的
// "strictNullChecks": true  // 单独开这一项,收益最大、破坏最小

名词解释

strictNullChecks:strict 家族里最重要的一项。开启后,null 和 undefined 不再是「能塞进任何类型的万能值」,而是必须被显式标注(T | null)并判空。它直接消灭了 JS 头号运行时错误「无法读取 null 的属性」。代价是老代码要补大量判空——但这是值得的。

渐进式严格化(Gradual Strictness):老项目不敢一次性开 strict 时,先单独开 strictNullChecks,再逐步开 noImplicitAny 等。像「先系安全带再提速」,既拿到最大收益(杜绝空指针),又不至于改动爆炸。

课后练习

练习 1:为什么 strictNullChecks 比 noImplicitAny 对运行时安全影响更大?

答案:noImplicitAny 主要防止「类型信息丢失导致提示变弱」;而 strictNullChecks 直接防止「把 null 当对象用」这种会崩的运行时代码被编译通过。前者影响开发体验,后者影响线上稳定性,所以后者更关键。

练习 2:类里 strictPropertyInitialization 报错「属性未初始化」怎么解?

答案:三种方式:① 声明时给默认值 name = '';② 构造函数里赋值;③ 确定运行时会赋值则用 name!: string(非空断言属性,谨慎用)。

总结

我的立场从不摇摆:新项目直接 "strict": true 起步,没有商量余地。它短期让你多写几行判空和注解,长期替你挡掉一整类最致命的线上事故。对老项目,我的迁移策略是「先开 strictNullChecks 单项」——这一项性价比最高、破坏最小,团队最容易接受;跑顺了再逐个加 noImplicitAny。千万别反过来「图省事先关 strict,以后再说」,因为「以后」永远不会来,欠的类型债越积越难还。记住:类型严格不是给编译器看的表演,是给你自己买的保险。