跳至主要內容

類型守衛

Flow 讓您可以定義其回傳表達式對參數 param 編碼一些類型謂詞的函式。此謂詞會註解在回傳類型註解的位置,為 param is PredicateType。它宣告如果函式回傳 true,則 param 的類型為 PredicateType

此類函式的語法為

function predicate(param: InputType): param is PredicateType {
return <some_expression>;
}

此函式的類型也可以用類型守衛註解來撰寫

type PredicateFunc = (param: InputType) => param is PredicateType;

基本用法

我們來看一個簡單的範例,其中我們定義一個類型守衛函式,然後使用它來精簡一些值。

定義類型守衛函式

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}

我們定義了一個資料類型 AorB,它是兩個類型 AB 的不相交聯集,每個類型都有用作標籤的屬性 type

我們也寫了一個針對類型 AorB 物件定義的 *使用者定義類型守衛* 函式 isA。當其輸入的 type 屬性值為 "A" 時,此函式會回傳 true。使用 AB 的定義,Flow 可以證明當 type 的值為 "A" 時,value 的類型將會是 A

使用類型防護函數精煉值

已宣告類型防護的函數可用於在條件式中精煉值。在上面的範例中,我們可以使用 isA 將類型為 AorB 的變數精煉為僅 A

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}8
9function test(x: AorB) {10  if (isA(x)) {11    // `x` has now been refined to type A.12    // We can assign it variables of type A ...13    const y: A = x;14    // ...and access A's properties through `x`15    const stringData: string = x.data;16
17    // As a sanity check, the following assignment to B will error18    const error: B = x;
19 }20}
18:22-18:22: Cannot assign `x` to `error` because string [1] is incompatible with number [2] in property `data`. [incompatible-type]
18:22-18:22: Cannot assign `x` to `error` because string literal `A` [1] is incompatible with string literal `B` [2] in property `type`. [incompatible-type]

在條件式的 then 分支 if (isA(x)) 中,x 將具有類型 A

使用 Array.filter 精煉

Flow 會辨識您在類型為 Array<T> 的陣列上呼叫 filter,並帶有包含類型防護的 callback 函數,類型為 (value: T) => value is S。它會使用這個產生類型為 Array<S> 的輸出陣列。請注意,S 必須是陣列元素 T 類型的子類型。

例如

1type Success = $ReadOnly<{type: 'success', value: 23}>;2type Error = $ReadOnly<{type: 'error', error: string}>;3
4type Response =5  | Success6  | Error7
8function filterSuccess(response: Array<Response>): Array<Success> {9  return response.filter(10    (response): response is Success => response.type === 'success'11  );12}13
14function filterError1(response: Array<Response>): Array<Error> {15  const result = response.filter(16    (response): response is Success => response.type === 'success'17  );18  // The following is expected to error19  return result;
20}21 22function filterError2(response: Array<Response>): Array<Error> {23 const result = response.filter(24 // The following is expected to error25 (response): response is Error => response.type === 'success'
26 );27 return result;28}
19:10-19:15: Cannot return `result` because property `error` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because property `value` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of array element. [incompatible-return]
25:38-25:64: Cannot return `response.type === 'success'` because property `error` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because property `value` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of the type inferred for type guard parameter `response` [3]. [incompatible-return]

filterError1 中,過濾會產生與預期回傳類型 Array<Error> 不相容的 Array<Success>

filterError2 中,謂詞 response.type === 'success' 用於將 Response 精煉為 Success,而非 Error

定義類型防護函數

為了確保使用類型防護函數的精煉是健全的,Flow 會執行與這些函數相關的數個檢查。

謂詞參數是函數的常規參數

在形式為 parameter is Type 的類型防護註解中,parameter 需要屬於目前函數的參數清單。

1function missing(param: mixed): prop is number {
2 return typeof param === "number";3}
1:33-1:36: Cannot find type guard parameter `prop` [1] in the parameters of this function (type). [function-predicate]

它不能是解構模式中綁定的參數,或 rest 參數

1function destructuring({prop}: {prop: mixed}): prop is number {
2 return typeof prop === "number";3}
1:48-1:51: A type guard parameter `prop` [1] cannot reference pattern parameter `prop` [2]. [function-predicate]
1function rest(...value: Array<mixed>): value is Array<mixed> {
2 return Array.isArray(value);3}
1:40-1:44: A type guard parameter `value` [1] cannot reference rest parameter `value` [2]. [function-predicate]

謂詞類型與參數類型一致

類型防護 Type 需要與參數類型相容。換句話說,給定定義

function isT(x: ParamType): x is Type {
return ...
}

Flow 會檢查 Type 是否為 ParamType 的子類型。因此,以下會是一個錯誤

1function isNumber(x: string): x is number {
2 return typeof x === "number";3}
1:36-1:41: Cannot use number [1] as type prediate for parameter `x` because number [1] is incompatible with string [2]. A user defined type guard needs to be compatible with its parameter's type. [incompatible-type-guard]

類型防護函數回傳布林值

類型防護函數需要回傳布林表達式。以下是非法的宣告

1function isNumberNoReturn(x: string): x is string {}
1:39-1:49: Cannot declare a type predicate [1] for function [2] because boolean [1] is incompatible with implicitly-returned undefined. [incompatible-return]
1function nonMaybe<V: {...}>(x: ?V): x is V {2  return x;
3}
2:10-2:10: Cannot return `x` because null or undefined [1] is incompatible with boolean [2]. [incompatible-return]
2:10-2:10: Cannot return `x` because object type [1] is incompatible with boolean [2]. [incompatible-return]

nonMaybe 的正確版本為

1function nonMaybe<V: {...}>(x: ?V): x is V {2  return !!x;3}

謂詞類型與精緻類型一致

除了上述檢查外,Flow 也會確保宣告的類型防護與函式主體中發生的檢查一致。為此,需要保證兩件事

  1. 套用回傳表達式謂詞之後,回傳位置的精緻參數類型為防護類型的子類型。例如,下列定義正確
1function numOrStr(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "string");3}4
5function numOrStrWithException(x: mixed): x is number | string {6  if (typeof x === "number") {7    return true;8  } else {9    if (typeof x === "string") {10        return true;11    } else {12        throw new Error("");13    }14  }15}

但在下列情況中,Flow 會產生錯誤

1function numOrStrError(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "boolean");
3}
2:36-2:57: Cannot return `((typeof x) === "number") || ((typeof x) === "boolean")` because in the type inferred for type guard parameter `x` [1]: [incompatible-return] Either boolean [2] is incompatible with number [3]. Or boolean [2] is incompatible with string [4].
  1. 類型防護函式主體中無法重新指派已精緻的參數。因此,下列為錯誤
1function isNumberError1(x: mixed): x is number {2  x = 1;3  return typeof x === "number";
4}
3:10-3:30: Cannot use type guard parameter `x` [1] because at this return point it is writen to in [2]. [function-predicate]
1function isNumberError2(x: mixed): x is number {
2 function foo() {3 x = 1;4 }5 foo();6 return typeof x === "number";7}
1:36-1:36: Cannot use type guard parameter `x`, because `x` [1] is reassigned in [2]. [function-predicate]

採用

若要使用類型防護,您需要升級您的基礎架構,使其支援語法

  • flowflow-parser:0.209.1。在 v0.209.1 至 v0.211.1 之間,您需要在 .flowconfig 中的 [options] 標題下明確啟用它,新增 type_guards=true
  • prettier: 3
  • babelbabel-plugin-syntax-hermes-parser。請參閱 我們的 Babel 指南 以取得設定說明。
  • eslinthermes-eslint。請參閱 我們的 ESLint 指南 以取得設定說明。