Typescript 中的弱类型检测

2021-02-28

Typescript 2.4 引入了弱类型(weak types)的概念。当一个类型的所有属性都是可选的时候 ,它被认为是弱类型。说得更具体一些,一个弱类型定义了一个或多个可选属性,没有必需属性,也没有索引签名。

举个例子,下面的类型被认为是弱类型:

interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

弱类型检测的主要目的是为了找出代码中可能的错误,避免成为潜在的 bug。看下面的例子:

interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true,
};

const formatter = createFormatter(prettierConfig); // Error

在 Typescript 2.4 之前,上面的这段代码是类型正确的。PrettierConfig 的所有属性都是可选的,所以一个属性都不设置也没问题。并且,我们的 pretteirConfig 对象有一个 semicolons 属性,这个属性并不存在于 PrettierConfig 类型中。

从 Typescript 2.4 开始,如果给弱类型赋值的对象中没有和弱类型相互重叠的属性(看文档),类型检查器会报以下的错误:

Type '{ semicolons: boolean; }' has no properties
in common with type 'PrettierConfig'.

虽然我们的代码严格说并没有错,但它很可能潜入了一个 bug。createFormatter 函数很可能会忽略 config 中它不认识的任何属性(比如 semicolons),而只使用它认识属性的默认值。在这种情况下,我们的 semicolons 属性不会有任何作用,无论它被设置为 true 或者 false

Typescript 弱类型检测能帮助我们找出函数调用中 prettierConfig 可能存在的问题。如此,我们就可以更早地知道某些潜在的问题。

显式的类型标注

除了依赖弱类型检测,我们可以显式地给 prettierConfig 对象添加类型标注:

const prettierConfig: PrettierConfig = {
  semicolons: true, // Error
};

const formatter = createFormatter(prettierConfig);

添加了显式标注后,我们得到了以下的类型错误:

Object literal may only specify known properties,
and 'semicolons' does not exist in type 'PrettierConfig'.

通过这种方式 ,类型错误会更就近提示。它会提示在我们错误定义 semicolons 属性的地方,而不是正确地将 prettierConfig 参数传给 createFormatter 函数的时候。

这样做另一个好处是,Typescript 会提供自动补全的建议,因为类型标注告知了我们创建的对象类型。

避免弱类型报错的方法

假如,出于某些原因,我们不希望对某个特定的弱类型检测并报错,该怎么做?一种方法是给 PrettierConfig 添加 unknown 类型的索引签名:

interface PrettierConfig {
  [prop: string]: unknown;
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true,
};

const formatter = createFormatter(prettierConfig);

现在,这段代码是类型正确的了,因为我们显式地允许 PrettierConfig 类型中有未知的属性。

或者,我们也可以使用类型断言来告诉类型检查器我们的 prettierConfig 对象是 PrettierConfig 类型:

interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true,
};

const formatter = createFormatter(prettierConfig as PrettierConfig);

我建议你避免使用类型断言来静默弱类型检测。可能是有一些场景使用类型断言是合适的,但一般而言,你应该选择其他更好的解决方案。

弱类型检测的局限

注意弱类型检测只会在完全没有属性重叠的情况下报类型错误。只要你设置了一个或多个弱类型中定义的属性,编译器将不会再报错误:

interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  printWidth: 100,
  semicolons: true,
};

const formatter = createFormatter(prettierConfig);

在上面的例子中,我同时设置了 printWidthsemicolons。因为 printWidth 存在于 PrettierConfig 中,所以对象和 PrettierConfig 类型有属性重叠,弱类型的检测不再对函数调用提示错误。

郑超的独立博客