跳至主要內容

類型精練

精練讓我們能根據條件測試縮小值的類型。

例如,在下方函式中,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)檢查會針對 nullvoid 精確檢查 value。這與 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 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}