Skip to main content

类型改进

细化使我们能够根据条件测试缩小值的类型。

¥Refinements allow us to narrow the type of a value based on conditional tests.

例如,在下面的函数中,value"A""B"联合

¥For example, in the function below value is a union of "A" or "B".

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

if 块内部,我们知道值必须是 "A",因为这是 if 语句唯一为 true 的时候。

¥Inside of the if block we know that value must be "A" because that's the only time the if-statement will be true.

静态类型检查器能够判断 if 语句内的值必须是 "A" 的能力称为细化。

¥The ability for a static type checker to be able to tell that the value inside the if statement must be "A" is known as a refinement.

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

¥Next we'll add an else block to our if statement.

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

else 块内部,我们知道值必须是 "B",因为它只能是 "A""B",并且我们已从可能性中删除了 "A"

¥Inside of the else block we know that value must be "B" because it can only be "A" or "B" and we've removed "A" from the possibilities.

Flow 中的优化方法

¥Ways to refine in Flow

typeof 检查

¥typeof checks

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

¥You can use a typeof value === "<type>" check to refine a value to one of the categories supported by the typeof operator.

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

¥The typeof operator can output "undefined","boolean", "number", "bigint", "string", "symbol", "function", or "object".

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

¥Keep in mind that the typeof operator will return "object" for objects, but also null and arrays as well.

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 平等 检查。

¥To check for null, use a value === null equality check.

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

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

¥To check for arrays, use Array.isArray:

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

相等检查

¥Equality checks

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

¥As shown in the introductory example, you can use an equality check to narrow a value to a specific type. This also applies to equality checks made in switch statements.

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 的 或许 类型,它与 nullvoid 创建联合。

¥While in general it is not recommended to use == in JavaScript, due to the coercions it performs, doing value == null (or value != null) checks value exactly for null and void. This works well with Flow's maybe types, which create a union with null and void.

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

你可以基于公共标签细化对象类型的联合,我们将其称为 不相交的对象联合

¥You can refine a union of object types based on a common tag, which we call disjoint object unions:

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}

真实性检查

¥Truthiness checks

你可以在 JavaScript 条件中使用非布尔值。0NaN""nullundefined 将全部强制转换为 false(因此被视为 "假的")。其他值将强制为 true(因此被视为 "真实的")。

¥You can use non-booleans in JavaScript conditionals. 0, NaN, "", null, and undefined will all coerce to false (and so are considered "falsey"). Other values will coerce to true (and so are considered "truthy").

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}

你可以在上面的示例中看到为什么当你的值可以是字符串或数字时不建议进行真实检查:可能会无意中检查 ""0。我们创建了一个名为 粗略空Flow lint 来防范这种情况:

¥You can see in the above example why doing a truthy check when your value can be a string or number is not suggested: it is possible to unintentionally check against the "" or 0. We created a Flow lint called sketchy-null to guard against this scenario:

1// flowlint sketchy-null:error2function func(value: ?string) {3  if (value) { // Error!4  }5}

in 检查

¥in checks

你可以使用 in 运算符检查对象上是否存在属性(在其自己的属性中,或原型链上)。这可用于细化对象的并集:

¥You can use the in operator to check if a property exists on an object (either in its own properties, or up the prototype chain). This can be used to refine a union of objects:

1function func(obj: {foo: string, value: boolean} | {bar: string, value: number}) {2  if ('foo' in obj) {3    obj.value as boolean; // Works!4  } else {5    obj.value as number; // Works!6  }7}

这最适合 精确对象 的并集,因为在否定中我们知道该属性不存在。我们不能对 不精确的对象接口实例类型 说同样的话,因为它们可能具有其他未知属性,包括我们正在检查的属性。此外,可选属性 可能存在也可能不存在,因此检查起来不是特别有用。

¥This works best on unions of exact objects, since in the negation we know the property does not exist. We cannot say the same for inexact objects, interfaces, and instance types, since they may have other unknown properties, including the one we are checking. Additionally, optional properties may or may not exist, so are not particularly useful to check against.

如果你想根据元素是否存在来细化 元组类型 的并集,请检查 length 属性,而不是尝试使用 in

¥If you want to refine a union of tuple types based on whether an element exists, check the length property instead of attempting to use in.

instanceof 检查

¥instanceof checks

你也可以使用 实例化 运算符来缩小值范围。它检查提供的构造函数的原型是否位于值原型链中的任何位置。

¥You can use the instanceof operator to narrow a value as well. It checks if the supplied constructor's prototype is anywhere in a value's prototype chain.

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(); // Error17  }18
19  if (value instanceof Object) {20    value.toString(); // Works21  }22}

赋值

¥Assignments

流遵循你的控制流,并在分配给变量后缩小变量的类型。

¥Flow follows your control flow and narrows the type of a variable after you have assigned to it.

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

类型保护

¥Type Guards

你可以通过定义 类型保护 函数来创建可重用的细化。

¥You can create a reusable refinement by defining a function which is a type guard.

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}

细化无效

¥Refinement Invalidations

也可以使细化无效,例如:

¥It is also possible to invalidate refinements, for example:

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

原因是我们不知道 otherFunc() 没有为我们的价值做一些事情。想象一下以下场景:

¥The reason for this is that we don't know that otherFunc() hasn't done something to our value. Imagine the following scenario:

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);

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

¥Inside of otherFunc() we sometimes remove prop. Flow doesn't know if the if (value.prop) check is still true, so it invalidates the refinement.

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

¥There's a straightforward way to get around this. Store the value before calling another function and use the stored value instead. This way you can prevent the refinement from invalidating.

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}