Menu

TypeScript 严格模式:strict: true 开启了什么

tsconfig.json 中的 strict: true 会开启一组类型检查:noImplicitAny、strictNullChecks、strictPropertyInitialization 以及另外五个。本文讲解每一项能捕获什么、如何在现有项目中开启严格模式,以及 strict 不包含的实用选项。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

严格模式就是 tsconfig.json 中的 "strict": true 选项。它是八个类型检查选项的总开关,能捕获隐式的 any、未检查的 null 和 undefined、不安全的函数赋值以及未初始化的类字段。在 TypeScript 7 中,strict 默认开启。能通过严格检查的代码是这样的:

输出是 no user、SyntaxError 和 42。去掉 : number、?. 或 instanceof 检查中的任何一个,文件就无法编译了。

开启严格模式

{
    "compilerOptions": {
        "strict": true
    }
}

单独的选项总是优先于 strict,两个方向都一样。"strict": true, "strictNullChecks": false 保留除空值检查以外的一切;"strict": false, "noImplicitAny": true 只开启这一项。strict 还会让你自动获得未来版本新增的检查,因为新的 strict 系列选项都会加入这一组。

TypeScript 的 strict 与 JavaScript 的 "use strict" 指令毫无关系,后者是一种运行时模式。TypeScript 7 会在需要的地方自动生成 "use strict",而设置 alwaysStrict: false 会报错 TS5108(该选项已被移除)。

每个选项能捕获什么

选项报告的问题错误
noImplicitAny类型会被悄悄当作 any 的参数或变量TS7006 Parameter 'x' implicitly has an 'any' type.
strictNullChecks使用可能为 null 或 undefined 的值TS18048 'u' is possibly 'undefined'.
strictFunctionTypes赋值一个参数类型比要求更窄的函数TS2322
strictBindCallApply给 .call、.bind 和 .apply 传错参数TS2345
strictPropertyInitialization从未被赋值的类字段TS2564 Property 'name' has no initializer and is not definitely assigned in the constructor.
noImplicitThis隐式为 any 类型的 this,比如在嵌套的 function 中TS2683
useUnknownInCatchVariables未经收窄就使用 catch 变量(它的类型是 unknown)TS18046 'err' is of type 'unknown'.
strictBuiltinIteratorReturn把内置迭代器的 it.next().value 当作总是有值TS2322

改动代码最多的两项是 noImplicitAny 和 strictNullChecks。下面这段代码故意同时违反了这两项:

它会输出 index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type. 和 index.ts(11,13): error TS18048: 'user' is possibly 'undefined'.。如果不开启严格模式,这段代码能编译通过,然后在运行时崩溃并报 TypeError: Cannot read properties of undefined (reading 'name')。开启严格模式后,运行时崩溃就变成了编译错误。修复方法就是本页的第一段代码。

strictFunctionTypes:为什么需要它

只能处理字符串的函数,不能用在可能传入数字的地方。下面的检查被 @ts-expect-error 压制住了,这样你可以运行它,看看这个错误阻止了什么:

去掉这行注释,这个赋值就会报错 TS2322:Type '(s: string) => void' is not assignable to type 'Handler'.,后面跟着 Types of parameters 's' and 'value' are incompatible.。有一个例外是有意保留的:用方法语法声明的方法参数(接口中的 handle(value: string | number): void)仍按较宽松的方式检查,所以把 shout 赋给这样的方法不会报错。

strictPropertyInitialization

每个类字段都必须在声明处或构造函数中获得一个值。有三种方式满足这一要求:

输出:

Account {
  owner: 'Ada',
  balance: 0,
  history: [],
  lastLogin: undefined,
  sessionId: 's-1'
}

lastLogin 和 sessionId 属性存在,值为 undefined,因为在 ES2022 目标下,类字段是真正的 JavaScript 字段。! 会去掉检查,但不提供任何运行时保护,所以优先使用其他写法。这个选项依赖 strictNullChecks,关掉后者它也会一起失效。

在现有项目中开启严格模式

一次性把大型代码库切换到 strict 可能会产生上百个错误。由于 strict 是 TypeScript 7 的默认值,升级一个 tsconfig.json 从未提到 strict 的项目时,它会自动开启;如果迁移期间需要旧的行为,就写上 "strict": false。下面这条路线可以让构建始终保持通过:

  1. 加上 "strict": true,并关闭错误最多的几个选项,通常是 "strictNullChecks": false 和 "noImplicitAny": false。
  2. 修复剩下的错误,然后再开启一个选项,重复这个过程。
  3. 对于 noImplicitAny,大多数修复就是给参数加类型注解。对于 strictNullChecks,在值可能缺失的地方加上 | undefined,再用 ?.、?? 或 if 判断来处理。
  4. 暂时没法修的地方,在那一行写上 // @ts-expect-error 并注明原因。与 @ts-ignore 不同,问题消失后它会报错,所以这份清单会自己慢慢变短。

不要用 as any 或 ! 来批量消除错误:每一个都恰好隐藏了该选项本来要找出的 bug。

strict 不包含的实用选项

这些选项是独立的,因为它们会拒绝很多其实正确的代码。不过很多项目还是会开启它们。

选项作用
noUncheckedIndexedAccessarr[i] 和 record[key] 的类型中包含 undefined
exactOptionalPropertyTypesdebug?: boolean 允许缺少这个键,但不允许 debug: undefined
noImplicitReturns有返回值的函数,每条代码路径都必须返回(TS7030)
noImplicitOverride重写基类方法的方法必须写 override(TS4114)
noFallthroughCasesInSwitch非空的 case 必须以 break、return 或 throw 结尾(TS7029)
noUnusedLocals、noUnusedParameters未使用的变量和参数会报错
noPropertyAccessFromIndexSignature来自索引签名的键必须用 obj["key"] 访问,而不是 obj.key

noUncheckedIndexedAccess 能捕获最多的真实 bug。开启后:

const scores = [90, 85];
const d: Record<string, number> = {};

const third: number = scores[2]; // error TS2322: Type 'number | undefined' is not assignable to type 'number'.
const c: number = d["x"];        // same error

const safe = scores[2] ?? 0;     // number
for (const s of scores) {        // for...of is not affected: s is number
    console.log(s);
}

TypeScript 7 中的 tsc --init 会在生成的配置里,在 strict 旁边开启 noUncheckedIndexedAccess 和 exactOptionalPropertyTypes。

常见问题

TypeScript 的严格模式有什么作用?

"strict": true 会开启一组更严格的检查:noImplicitAny、strictNullChecks、strictFunctionTypes、strictBindCallApply、strictPropertyInitialization、noImplicitThis、useUnknownInCatchVariables 和 strictBuiltinIteratorReturn。它们合在一起,能阻止 any 悄悄出现,并让 null 和 undefined 成为类型系统的一部分。

TypeScript 默认开启严格模式吗?

在 TypeScript 7 中是的:strict 默认为 true,所以没有设置 strict 的项目也会获得所有严格检查。tsc --init 也会显式写入 "strict": true。想关闭它,必须写 "strict": false。

能只关闭某一项严格检查,保留其余的吗?

可以。单独的选项会覆盖 strict:{ "strict": true, "strictNullChecks": false } 保留除空值检查以外的所有严格检查。这是一次迁移一个选项、逐步改造大型代码库的常用做法。

TypeScript 严格模式和 JavaScript 的 "use strict" 是一回事吗?

不是。"use strict" 是 JavaScript 的运行时模式,会改变代码的行为。TypeScript 的 strict 只改变类型检查器报告的内容。TypeScript 7 对非模块输出总会生成 "use strict",而 alwaysStrict: false 现在是一个已移除的选项。

strict 包含 noUncheckedIndexedAccess 吗?

不包含。noUncheckedIndexedAccess、exactOptionalPropertyTypes、noImplicitReturns、noImplicitOverride 和 noFallthroughCasesInSwitch 都是需要你自己开启的独立选项。tsc --init 会在生成的配置中开启前两个。

Coddy 编程语言插图

和 Coddy 一起学编程

开始