跳至主要内容

类型细化

细化允许我们根据条件测试缩小值的类型。

例如,在下面的函数中,value 是一个 联合 类型,可以是 "A""B"

1function func(value: "A" | "B") {2  if (value === "A") {3    value as "A";4  }5}

if 块中,我们知道 value 必须是 "A",因为只有在这种情况下 if 语句才会为真。

静态类型检查器能够识别 if 语句中的值必须是 "A" 的能力被称为细化。

接下来,我们将向 if 语句添加一个 else 块。

1function func(value: "A" | "B") {2  if (value === "A") {3    value as "A";4  } else {5    value as "B";6  }7}

else 块中,我们知道 value 必须是 "B",因为它只能是 "A""B",并且我们已经排除了 "A"

在 Flow 中进行细化的方式

typeof 检查

您可以使用 typeof value === "<type>" 检查将值细化为 typeof 运算符支持的类别之一。

typeof 运算符可以输出 "undefined""boolean""number""bigint""string""symbol""function""object"

请记住,typeof 运算符将对对象返回 "object",但也会对 null 和数组返回 "object"

1function func(value: mixed) {2  if (typeof value === "string") {3    value as string;4  } else if (typeof value === "boolean") {5    value as boolean;6  } else if (typeof value === "object") {7    // `value` could be null, an array, or an object8    value as null | interface {} | $ReadOnlyArray<mixed>;9  }10}

要检查 null,请使用 value === null 相等性 检查。

1function func(value: mixed) {2  if (value === null) {3    value as null; // `value` is null4  }5}

要检查 数组,请使用 Array.isArray

1function func(value: mixed) {2  if (Array.isArray(value)) {3    value as $ReadOnlyArray<mixed>; // `value` is an array4  }5}

相等性检查

如介绍性示例所示,您可以使用相等性检查将值缩小到特定类型。这也适用于在 switch 语句中进行的相等性检查。

1function func(value: "A" | "B" | "C") {2  if (value === "A") {3    value as "A";4  } else {5    value as "B" | "C";6  }7
8  switch (value) {9    case "A":10      value as "A";11      break;12    case "B":13      value as "B";14      break;15    case "C":16      value as "C";17      break;18  }19}

虽然通常不建议在 JavaScript 中使用 ==,因为它会执行强制转换,但使用 value == null(或 value != null)会精确地检查 value 是否为 nullvoid。这与 Flow 的 maybe 类型配合良好,该类型会创建一个包含 nullvoid 的联合类型。

1function func(value: ?string) {2  if (value != null) {3    value as string;4  } else {5    value as null | void;6  }7}

您可以根据共同的标签细化对象类型联合,我们称之为 不相交对象联合

1type A = {type: "A", s: string};2type B = {type: "B", n: number};3
4function func(value: A | B) {5  if (value.type === "A") {6    // `value` is A7    value.s as string; // Works8  } else {9    // `value` is B10    value.n as number; // Works11  }12}

真值检查

您可以在 JavaScript 条件语句中使用非布尔值。0NaN""nullundefined 都将强制转换为 false(因此被认为是“假值”)。其他值将强制转换为 true(因此被认为是“真值”)。

1function func(value: ?string) {2  if (value) {3    value as string; // Works4  } else {5    value as null | void; // Error! Could still be the empty string ""
6 }7}
5:5-5:9: Cannot cast `value` to union type because string [1] is incompatible with literal union [2]. [incompatible-cast]

您可以在上面的示例中看到,当您的值可以是字符串或数字时,进行真值检查为什么不建议:您可能会无意中检查 ""0。我们创建了一个名为 Flow 代码风格检查sketchy-null 来防止这种情况。

1// flowlint sketchy-null:error2function func(value: ?string) {3  if (value) { // Error!
4 }5}
3:7-3:11: Sketchy null check on string [1] which is potentially an empty string. Perhaps you meant to check for null or undefined [2]? [sketchy-null-string]

instanceof 检查

您也可以使用 instanceof 运算符来缩小值。它检查提供的构造函数的原型是否在值的原型链中的任何位置。

1class A {2  amaze(): void {}3}4class B extends A {5  build(): void {}6}7
8function func(value: mixed) {9  if (value instanceof B) {10    value.amaze(); // Works11    value.build(); // Works12  }13
14  if (value instanceof A) {15    value.amaze(); // Works16    value.build(); // Error
17 }18 19 if (value instanceof Object) {20 value.toString(); // Works21 }22}
16:11-16:15: Cannot call `value.build` because property `build` is missing in `A` [1]. [prop-missing]

赋值

Flow 会跟踪您的控制流,并在您对变量进行赋值后缩小其类型。

1declare const b: boolean;2
3let x: ?string = b ? "str" : null;4
5x as ?string;6
7x = "hi";8
9// We know `x` must now be a string after the assignment10x as string; // Works

类型守卫

您可以通过定义一个充当 类型守卫 的函数来创建可重用的细化。

1function nonMaybe<T>(x: ?T): x is T {2  return x != null;3}4
5function func(value: ?string) {6  if (nonMaybe(value)) {7    value as string; // Works!8  }9}

细化失效

细化也可能失效,例如

1function otherFunc() { /* ... */ }2
3function func(value: {prop?: string}) {4  if (value.prop) {5    otherFunc();6    value.prop.charAt(0); // Error!
7 }8}
6:16-6:21: Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]

原因是,我们不知道 otherFunc() 是否对我们的值进行了操作。想象一下以下场景

1const obj: {prop?: string} = {prop: "test"};2
3function otherFunc() {4  if (Math.random() > 0.5) {5    delete obj.prop;6  }7}8
9function func(value: {prop?: string}) {10  if (value.prop) {11    otherFunc();12    value.prop.charAt(0); // Error!
13 }14}15 16func(obj);
12:16-12:21: Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]

otherFunc() 中,我们有时会删除 prop。Flow 不知道 if (value.prop) 检查是否仍然为真,因此它会使细化失效。

有一个简单的方法可以解决这个问题。在调用另一个函数之前存储该值,然后使用存储的值。这样可以防止细化失效。

1function otherFunc() { /* ... */ }2
3function func(value: {prop?: string}) {4  if (value.prop) {5    const prop = value.prop;6    otherFunc();7    prop.charAt(0);8  }9}