优化以在联合中键入别名

Refine to type alias in union

我正在尝试检查联合类型的值是否是联合类型之一。

在下面的代码中,前三种方法 (1-3) 似乎不正确。

检查特定 属性 方法 (4) 似乎有效,但不清楚它已细化为哪种类型。

  1. (4)(AB)细化为哪一类?
  2. 为什么不能将数字或字符串分配给 (5) 和 (6) 中的 a1.a
  3. 如果联合中的类型不是原始类型(即类型别名),哪种类型是细化到其中一种类型的正确方法?
type A = {
  a: number
}

type B = {
  a: string
}

const a1: A | B = {a: 1}

if (typeof(a1) === 'A') {}    // (1) Cannot compare the result of `typeof` to string literal `A` because it is not a valid `typeof` return value
if (a1 instanceof A) {}       // (2) Cannot reference type `A` [1] from a value position
(a1: A)                       // (3) Cannot cast `a1` to `A` because string [1] is incompatible with `A` [2]

if (a1.a) {                   // (4) Works, but inconclusive?
  a1.a = 2                    // (5) Cannot assign `2` to `a1.a` because number [1] is incompatible with string [2]
  a1.a = '2'                  // (6) Cannot assign `'2'` to `a1.a` because string [1] is incompatible with number [2]
}

首先,我将浏览这段代码,只是为了弄清楚发生了什么:

type A = {
  a: number
}

type B = {
  a: string
}

const a1: A | B = {a: 1}

常量 a1 已被声明为两个对象类型 AB 的联合类型。

if (typeof(a1) === 'A') {}    // (1) Cannot compare the result of `typeof` to string literal `A` because it is not a valid `typeof` return value

这里的问题是我们混淆了值级别和类型级别。这有点令人困惑,因为流共享它的 type-level typeof keyword with javascript's value-level typeof。这是两个不同的东西,不能互操作。我们可以将类型级别的流 typeof 语法视为编译时操作,将值级别的 javascript typeof 语法视为运行时操作。这种区别的重要之处在于,虽然流在编译时具有 一些 值意识,但 javascript 具有 没有 类型意识在运行时。

所以在这个例子中,我们得到一个运行时值 typeof(a1) 并试图弄清楚如何将它与编译时类型 A 进行比较,并使用此比较进行类型优化。这样做的问题是类型细化必须在编译时和运行时都进行操作,但运行时将不知道类型 A。在这里我们试图弄清楚如何引用 A 因为流没有给我们一个方法来做到这一点:

if (typeof(a1) === 'A') // How can we refer to `A` at runtime? We can't.

这里的另一个问题是 typeof a1 在所有情况下都会 return 'object' 因为这实际上只是常规 javascript 运行时 typeof关键字,以及 typeof returns 'object' 用于所有对象实例。

if (a1 instanceof A) {}       // (2) Cannot reference type `A` [1] from a value position

这基本上是同一个问题,但在这个例子中更清楚发生了什么。 Flow 准确地告诉我们问题出在哪里,我们试图从值位置而不是类型位置引用类型 (A)。 instanceof 是运行时检查,因此不能用于与类型进行比较。

(a1: A)                       // (3) Cannot cast `a1` to `A` because string [1] is incompatible with `A` [2]

这里我们尝试 type cast / assertion,这基本上不起作用,因为 a1 还没有细化为 A,就流程所知可能还是B,不处理这种情况是不安全的

if (a1.a) {                   // (4) Works, but inconclusive?
  a1.a = 2                    // (5) Cannot assign `2` to `a1.a` because number [1] is incompatible with string [2]
  a1.a = '2'                  // (6) Cannot assign `'2'` to `a1.a` because string [1] is incompatible with number [2]
}

这实际上是一个有效的细化操作,但它只细化了属性 a1.a。例如我们可以这样做:

type A = {
  member: string,
};

type B = {

};

declare var c: A | B;

if (c.member) {
  console.log(c.member); // c.member is of type `mixed`
}

(try)

这里我们将 c.member 细化为 existing,但我们对它一无所知,所以它的类型是 mixed。这意味着我们不能用它做那么多。这里重要的是,c 的类型仍然是 A | B,改进它的 属性 根本没有改进包含类型。

现在让我们进入真正的问题:

Which would be the correct way to refine to one of the types in a union, where the type is not a [union of] primitive [types] (i.e. is a [union of object types])?

如果目标是在顶层实际细化某物的对象类型,目前只有一种方法可以在流程中做到这一点:对联合体的每个成员使用 disjoint union. A disjoint union relies on a special flag member of object types that must be of a different literal type 这意味着如果你使用类似 string 的东西,它不会被识别为不相交的联合:

type A = {
  // We're calling this `type` but it could have any name as long
  // as it's the same among each member of the union.
  type: 'a', // This must be a literal type.
  aMember: string,
};

type B = {
  type: 'b', // This must be a different literal type.
  bMember: number,
};

declare var c: A | B;

if (c.type === 'a') {
  // Within this branch, type of `c` is `A`.
  (c.aMember: string);
  // $FlowFixMe
  (c.bMember: number);
} else {
  // Within this branch, type of `c` is `B`.
  (c.bMember: number);
  // $FlowFixMe
  (c.aMember: string);
}

(try)