類型精練
精練讓我們能根據條件測試縮小值的類型。
例如,在下方函式中,value 是 "A" 或 "B" 的 聯集。
1function func(value: "A" | "B") {2 if (value === "A") {3 value as "A";4 }5}在 if 區塊中,我們知道值必須是 "A",因為只有在 if 敘述為 true 時,才會執行。
靜態類型檢查器能夠判斷 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 區塊中,我們知道值必須是 "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)檢查會針對 null 和 void 精確檢查 value。這與 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 lint,稱為 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]
指定
流程會遵循您的控制流程,並在您指定變數後縮小其類型。
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}