当我将类型验证代码移动到外部函数时,Typescript 报告错误 "property does not exist on type"

Typescript reports error "property does not exist on type" when I move type validation code to an external function

我有一个函数可以检查对象是否属于某种类型。如果不是,那么我想抛出一个错误(我在测试中做了所有这些)。

有趣的是,当我将我的错误抛出类型验证代码移动到外部函数时,我得到了 Typescript 错误,但如果我将它与其他代码一起保留,Typescript 会得到它并且不会抱怨属性不存在(即 Property 'vehicle' does not exist on type 'SomeParentType')。

换句话说,这个有效:

function appendToOutputString(obj: SomeParentType) {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
  outputString += obj.vehicle;
}

但这工作

function appendToOutputString(obj: SomeParentType) {
  expectIsChildTypeA(obj)
  outputString += obj.vehicle;
}

这是我的完整代码的 link:https://stackblitz.com/edit/checking-type-in-function?file=index.ts

这是它的粘贴:

interface SomeParentType {
  title: string;
}

interface SomeChildTypeA extends SomeParentType {
  vehicle: string;
}

interface SomeChildTypeB extends SomeParentType {
  animal: string;
}

let outputString = "";

function isChildTypeA(childType: SomeParentType): childType is SomeChildTypeA {
  return "vehicle" in childType;
}

function expectIsChildTypeA(obj: any) {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
}

function appendToOutputString(obj: SomeParentType) {
  // if (!isChildTypeA(obj)) {
  //   throw new Error("Must be of type ChildA!");
  // }
  expectIsChildTypeA(obj)
  outputString += obj.vehicle;  // Typescript complains!!
}

// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById("app");
appDiv.innerHTML = `<h1>${outputString}</h1>`;

编译器有时可以通过 analyzing the control flow 识别出变量的类型比代码中某些点的注释或推断类型窄。在下面的代码块中,如果控制流到达 outputString += obj.vehicle 调用,编译器会理解 obj 必须是 SomeChildTypeA,因此没有错误:

if (!isChildTypeA(obj)) {
  throw new Error("Must be of type ChildA!");
}
outputString += obj.vehicle;  // okay

不过,正如您所发现的那样,简单地重构代码以使检查发生在不同的函数中是行不通的。编译器在执行控制流分析时,一般不会按照控制流进入函数和方法。这是一个 tradeoff (see Microsoft/TypeScript#9998 in GitHub for more info):编译器通过分析所有可能的函数调用的可能控制流路径,在所有可能的输入上模拟程序 运行 是不可行的,因此它必须在某处使用启发式;在这种情况下,启发式通常是 "assume function calls have no impact on variable types"。因此,调用 expectIsChildTypeA(obj) 对编译器所见的 obj 的类型没有影响,因此它会抱怨 obj.vehicle.


幸运的是,TypeScript 3.7 introduced "assertion functions";你可以给一个函数一个特殊的 return 类型 告诉编译器传递给函数的变量将被函数缩小,并且它将使用它作为它的一部分控制流分析。虽然编译器本身不会推断此类函数签名,但至少现在您可以手动注释 defined() 对其参数断言:

function expectIsChildTypeA(obj: any): asserts obj is SomeChildTypeA {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
}

expectIsChildTypeA()的return类型是asserts obj is SomeChildTypeA,也就是说obj如果函数return会被验证为SomeChildTypeA秒。这修复了您的原始示例:

expectIsChildTypeA(obj)
outputString += obj.vehicle;  // okay

看起来不错。好的,希望有所帮助;祝你好运!

Playground link to code