TypeScript 中的严格模式

14,369 阅读3分钟

在 tsconfig.json 文件中,有一个配置项叫做 strict,默认为 true,表示开启严格模式:

{
    /* Strict Type-Checking Options */
    "strict": true,                       /* Enable all strict type-checking options. */
}

TypeScript 中的严格模式跟 JavaScript 中说的严格模式(即 use strict)不是一个概念,它表示是否开启下面一系列的类型检查规则:

{
    "noImplicitAny": true,
    "strictNullChecks": true, 
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
}

意思是说:

  • 如果你设置 strict 为 true 的话,那么上面所有的配置默认全是 true
  • 如果你设置 strict 为 false 的话,那么上面的所有配置默认全是 false
  • 你如果既设置了 strict 又单独设置了其中的部分配置,那么以单独设置的配置为准

知道了这层关系之后,接下来就逐个解释 strict 所关联的配置项分别代表什么含义:

noImplicitAny

不允许出现隐式的 any 类型。下图声明了一个判断奇偶的函数:

noImplicitAny

TypeScript 是可以进行自动类型推断的,nums 被推断成数字类型的数组,isOddNumber 被推断成返回布尔值的函数,但是参数 num 没有定义类型,TypeScript 只能推断为 any 类型,这就等价于写了下面的代码:

const nums: number[] = [1, 2, 3]
const isOddNumber = (num: any): boolean => (num % 2) !== 0

这样的代码是有 bug 的,例如:

isOddNumber('a') // true
isOddNumber(undefined) // true

建议开启 noImplicitAny 选项。

strictNullChecks

严格空检查。如果关闭此选项,则 null 和 undefined 两种类型是任意类型的子类型,即下面的语法不会报错:

let num: number = 1
let str: string = 'hello'
let student: { name: string; age: number }  = { name: '小明', age: 12}

num = str = student = null
num = str = student = undefined

这样有可能引发未知的结果甚至 runtime error,因为下面的取值或运算都是合法的:

console.log(num + 10) // NaN
console.log(student.age) // TypeError: Cannot read property 'age' of undefined

建议开启 noImplicitAny 选项。

strictFunctionTypes

严格函数类型检查。我们知道,下面的赋值语法在 TypeScript 中是不允许的:

strictFunctionTypes

但可笑的是,在不开启 strictFunctionTypes 的情况下,如果你写出下面的代码,TypeScript 竟然是允许的:

function getCurrentYear(callback: (date: string | number) => void) {
  callback(Math.random() > 0.5 ? '2020' : 2020)
}
getCurrentYear((date: string) => {
  console.log(date.charAt(0))
})

真是毫无道理,所以强烈建议开启 strictFunctionTypes 选项。

strictBindCallApply

严格绑定检查。意思是在使用 bind、call 和 apply 语法的时候,是否进行参数检查,如果不开启的话,效果如下:

strictBindCallApply

这明显是不合理的,因为如果通过 call 就能绕过了 TypeScript 的类型检查的话,代码中肯定会出现隐藏的 bug,所以强烈建议开启。

strictPropertyInitialization

严格属性初始化检查。这个选项要和 strictNullChecks 配合使用才行,否则会出现下面的报错:

strictPropertyInitialization

该选项用于检查类的属性是否被初始化,如果开启则必须进行初始化,否则会出现下面的报错:

strictPropertyInitialization-error

解决方法就是:

class User {
  name: string = "";
  setName(name: string) {
    this.name = name;
  }
}

建议开启 strictPropertyInitialization 选项。

noImplicitThis

不允许出现隐式any类型的this。JavaScript 是一门非常灵活的语言,TypeScript 对于 this 并不能很好地做类型推断,只能推断其为 any 类圆形了,例如像下面这种情况:

noImplicitThis.png

这种 any 类型的 this 在理解上是有歧义的,例如:

new Person('小明').say().call({name: '小红'})
// 小明
// hello, my name is 小红

建议开启 noImplicitThis 选项。

alwaysStrict

是否开启严格模式。这个不用多说,就是 JavaScript 中的 "use strict",肯定是要开启的。