类型细化
细化允许我们根据条件测试缩小值的类型。
例如,在下面的函数中,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
是否为 null
和 void
。这与 Flow 的 maybe 类型配合良好,该类型会创建一个包含 null
和 void
的联合类型。
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 条件语句中使用非布尔值。0
、NaN
、""
、null
和 undefined
都将强制转换为 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}