使用 typeof === 自定义类型进行类型检查并出现 Flow 错误

Type check with typeof === custom type with Flow error

给定以下代码:

type CustomTypeA = {
  x: number,
  y: number,
}

type CustomTypeB = CustomTypeA & {
  z: number,
}

type CustomType = CustomTypeA | CustomTypeB

export const myFunction = (a: CustomType | number): number | void => {
  if (typeof a === 'number') {
    return a; // THIS WORKS WELL, it's considered number after the check, which is correct
  } else if (typeof a === CustomType) {
    
    const newX = a.x // ERR: Flow: Cannot get `a.x` because property `x` is missing in `Number`
    const newZ = a.z // SAME ERR, Flow: Cannot get `a.z` because property `z` is missing in `Number`.
  }
}

此外,typeof a === CustomType 检查也被突出显示为错误:

Flow: Cannot reference type `CustomType` from a value position.

但是 typeof a === 'number' 不会发生这种情况。

好像我创建的自定义对象类型的检查不是valid/recognized。

有人可以解释为什么以及如何避免这种情况吗? 谢谢

Flow 自定义类型不是值,它们不存在,它们在编译后消失,因此你不能将它们与 typeof 之类的 JS 运算符一起使用,因为它需要一个值。所以当你做 typeof a === CustomType 时它会失败,因为在编译后你将以 typeof a === 结束,CustomType 被删除并且你以无效的 JS 结束。

老实说,这似乎是一个流量限制。

%checks 运算符允许您构建类型保护函数。 有人可能认为您可以使用此功能通过具有正确逻辑的函数为您的自定义类型构建类型细化,但其文档中没有任何内容表明它可用于细化自定义类型。 它还要求 guard 函数的主体非常简单,以便流可以理解你的意思。某些类型保护函数示例可能如下所示 (from flow docs):

function truthy(a, b): boolean %checks {
  return !!a && !!b;
}

function isString(y): %checks {
  return typeof y === "string";
}

function isNumber(y): %checks {
  return typeof y === "number";
}

然而,当您尝试更复杂的检查时,例如检查某物是一个对象,但它不是数组或日期,流程无法理解您的意图并且谓词函数将不起作用。像这样:

function isObject(obj: mixed): boolean %checks {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

将失败,因为流不将其理解为对象的类型优化。对于这种特殊情况,有一个 workaround suggested on a github issue 包含在类型级别上声明函数,断言它检查对象类型:

declare function isObject(obj: mixed): boolean %checks(obj instanceof Object)

但是你不能将它用于你的特定情况,因为你不能对自定义类型执行 instanceof,因为它不是 class。

所以你的选择要么变得冗长,要么在类型检查中检查所有预期的属性,如下所示:

  if (typeof a.x === 'number' && typeof a.y === 'number' && typeof a.z === 'number') {
    const {x: ax, y: ay, z: az} = a
    // now you can safely use the extracted variables

请注意,您需要从对象中提取道具,因为任何时候您调用函数流都会使您的类型优化无效,并且访问 a.x 的下一行将失败。

您可以将您的观点声明为 Class,并使用类型系统检查该 class 的实例。

或者您构建一个 returns 正确类型或 null 的验证函数,以便流可以理解类型已被细化:

function isCustomType (p: mixed): CustomType | null {
  const validated = ((p:any):CustomType)
  if (typeof validated.x === 'number' && typeof validated.y === 'number') return validated
  return null
}

  const validA = isCustomType(a)
  if (validA) {
    const {x: ax, y: ay} = validA
    // do stuff

这样做的缺点是你需要分配额外的变量来满足类型系统,但我认为这是一个小问题。 此外,它不会允许 flow 为您验证 isCustomType 函数,因为我们正在进行类型转换以基本上欺骗 flow。但是考虑到表面很小而且 objective 非常集中,能够手动保持正确应该没问题。