跳至主要内容

类型守卫

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

我们还编写了一个用户定义的类型守卫函数 isA,它定义在类型 AorB 的对象上。当其输入的 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]

在条件语句 if (isA(x)) 的 then 分支中,x 的类型将为 A

使用 Array.filter 细化

Flow 识别您在类型为 Array<T> 的数组上调用 filter 时,使用回调函数持有类型守卫,其类型为 (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<Success>,它与预期的返回类型 Array<Error> 不兼容。

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]

它不能是解构模式中绑定的参数,也不能是剩余参数

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 指南