在TypeScript中控制类型检查的严格性

753 阅读5分钟

在这篇文章中,我们将探讨控制TypeScript中类型检查的严格性的编译器选项。

了解TypeScript严格模式

有一个TypeScript编译器选项叫做strict 。它开启了一系列的类型检查规则,被称为严格模式。这与JavaScript的严格模式是分开的。

当创建一个新的TypeScript项目时,建议打开严格模式,这样代码从一开始就能从最严格的类型检查中受益。然而,在将JavaScript代码库迁移到TypeScript时,严格模式可能并不可行,因为会产生大量的类型错误。

除了strict 选项,每个类型检查规则都可以通过特定的编译器选项来控制。这给了类型检查器的严格性以细粒度的控制。

理解noImplicitAny

noImplicitAny 编译器选项将在具有隐含类型的表达式和声明中引发错误any 。当strict 选项为true 时,该选项默认为true

下面的greet 方法目前违反了这个规则,因为prefix 参数被推断为具有any 类型:

public greet(prefix) {
  ...
}

我们可以将tsconfig.json 中的noImplicitAny 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "noImplicitAny": false
  },
  ....
}

为了正确解决这个错误,我们将给prefix 参数一个类型注释:

public greet(prefix: string) {
  ...
}

理解noImplicitThis

noImplicitThis 编译器选项在引用this 的表达式上引发了一个错误,该表达式隐含了any 类型。当strict 选项为true 时,该选项默认为true

下面的goodbyeFunction 方法在访问this 中的name 属性时违反了这个规则:

class Person {
  constructor(private name: string) {}

  goodbyeFunction() {
    return function() {
      console.log(`Goodbye ${this.name}`);
    };
  }
}

TypeScript将this 推断为any 类型,因此引发了一个类型错误。

我们可以将tsconfig.json 中的noImplicitThis 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "noImplicitThis": false
  },
  ....
}

有两种方法可以正确解决这个错误。

解决类型错误的第一个方法是让TypeScript知道函数签名中this 的类型:

goodbyeFunction() {
  return function (this: Person) {
    ...
  };
}

解决类型错误的第二种方法是使用一个箭头函数:

goodbyeFunction() {
  return () => {
    ...
  };
}

很好!

理解strictBindCallApply

strictBindCallApply 编译器选项可以对函数上的bind,call, 和apply 方法进行更严格的检查。当strict 选项为true 时,该选项默认为true

下面对person.calculatePrice 的调用违反了这个规则,因为calculatePrice 需要传入两个参数。函数类型是(discountCode: string, price: number) => number

const discountedPrice = person.calculatePrice.apply(
  undefined,
  ["FREE"]
);

我们可以将tsconfig.json 中的strictBindCallApply 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "strictBindCallApply": false
  },
  ....
}

为了正确解决这个错误,我们应该在函数调用中传递第二个参数:

const discountedPrice = person.calculatePrice.apply(
  undefined,
  ["FREE", 100]
);

了解strictNullChecks

strictNullChecks 编译器选项将nullundefined 的值排除在每个类型的域之外。当strict 选项为true 时,该选项默认为true

在下面的代码中,将person 赋值给null 违反了这个规则,因为null 不在Person 类型之内:

let person = new Person();
...
person = null;

我们可以将tsconfig.json 中的strictNullChecks 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "strictNullChecks": false
  },
  ....
}

为了更好地解决这个错误,我们可以在一个联合类型中把null 添加到person 的类型中:

let person: Person | null = new Person();
...
person = null;

了解strictFunctionTypes

strictFunctionTypes 禁用函数类型的双变量参数检查。当strict 选项为true 时,该选项默认为true

下面的getDiscountCode 函数包含一个类型错误,因为info 参数包含一个不属于APIRequestHandlertoken 属性:

type APIRequestHandler<T> = (info: { path: string }) => Promise<T>;

type APIRequestInfo = { path: string };
const getDiscountCode: APIRequestHandler<string> = async (
  info: APIRequestInfo & { token: string }
) => { ... }

我们可以将tsconfig.json 中的strictFunctionTypes 编译器选项设置为false 来消除这个类型错误:

{
  "compilerOptions": {
    ...
    "strictFunctionTypes": false
  },
  ....
}

解决这个类型错误并不直接。getDiscountCode 明确要求使用token属性。我们可以改变APIRequestHandler 的类型,但这可能会影响其他代码。一个解决方案是删除getDiscountCode 中的类型注释,让TypeScript推断其类型。

了解strictPropertyInitialization

strictPropertyInitialization 编译器选项确保非未定义的类属性在构造函数中被初始化。这个选项需要启用strictNullChecks 才能生效。当strict 选项为true 时,strictPropertyInitialization 默认为true

下面Person 类型内的name 属性目前违反了这一规则:

class Person {
  public name: string;
}

我们可以将tsconfig.json 中的strictPropertyInitialization 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "strictPropertyInitialization": false
  },
  ....
}

我们可以给name 赋予一个默认值来正确解决这个错误:

class Person {
  public name: string = "";
}

了解alwaysStrict

alwaysStrict 编译器选项确保JavaScript严格模式被用于类型检查过程。它也决定了"use strict" 是否在JavaScript中被排放出来。当strict 选项为true ,该选项默认为true

alwaysStrict 时,"use strict" 将在转置的JavaScript文件的顶部:

"use strict";
class Person ...

在JavaScript严格模式下,你不能像下面这样给一个变量命名为 "arguments":

let arguments;

我们可以将tsconfig.json 中的alwaysStrict 编译器选项设置为false 来消除类型错误:

{
  "compilerOptions": {
    ...
    "alwaysStrict": false
  },
  ....
}

然而,正确解决JavaScript严格模式的错误是明智的。在这种情况下,我们会把这个变量重命名为其他的东西:

let args;

总结

TypeScript有一系列的选项来控制类型检查的严格程度:

  • strict - 设置最大的严格性,将下面所有的标志设置为 。true
  • noImplicitAny - 对于推断类型为 的表达式和声明,会引发错误。any
  • noImplicitThis - 在推断类型为 的 表达式上引发错误。any this
  • strictBindCallApply - 启用对函数的 , , 和 方法的严格检查。bind call apply
  • strictNullChecks - 意味着 和 的值在类型中是无效的值。null undefined
  • strictFunctionTypes - 禁用对函数类型的双变量参数检查。
  • strictPropertyInitialization - 确保类属性被分配一个默认值或在构造函数中被初始化。
  • alwaysStrict - 在JavaScript严格模式下解析代码,并在转译后的代码中发出 "use strict"。

对于新项目,建议将strict 设置为true

true 对于JavaScript迁移项目,如果相应的规则引发了大量的类型错误,可以将strict ,其他标志设置为false 。一个目标可以是解决类型错误,并随着时间的推移将所有的strict 标志设为true