Skip to main content

类型保护

Flow 允许你定义函数,其返回表达式对参数 param 上的某种类型谓词进行编码。该谓词被注释为 param is PredicateType,以代替返回类型注释。它声明如果函数返回 true,则 param 的类型为 PredicateType

¥Flow lets you define functions whose return expression encodes some type predicate over a parameter param. This predicate is annotated in place of a return type annotation as param is PredicateType. It declares that if the function returns true then param is of type PredicateType.

像这样的函数的语法是:

¥The syntax for a function like this is:

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

该函数的类型也可以用类型保护注释来编写:

¥The type of this function can also be written in terms of a type guard annotation:

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

基本用法

¥Basic Usage

让我们看一个简单的例子,我们定义一个类型保护函数,然后用它来优化一些值。

¥Let's see a simple example where we define a type guard function and then use it to refine some values.

定义类型保护函数

¥Defining a type guard function

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

¥We have defined a data type AorB that is a disjoint union of two types A and B that each have a property type used as tag.

我们还编写了一个用户定义的类型保护函数 isA,该函数在 AorB 类型的对象上定义。当其输入的 type 属性的值为 "A" 时,此函数返回 true。利用 AB 的定义,Flow 可以证明当 type 的值为 "A" 时,value 的类型将为 A

¥We have also written a user defined type guard function isA defined over objects of type AorB. This function returns true when the value of of the type property of its input is "A". Using the definitions of A and B, Flow can prove that when the value of type is "A" then the type of value will be A.

使用类型保护函数来优化值

¥Using a type guard function to refine values

具有声明的类型保护的函数可用于细化条件中的值。在上面的示例中,我们可以使用 isAAorB 类型的变量细化为 A

¥Functions that have a declared type guard can be used to refine values in conditionals. In the example above, we can use isA to refine a variable of type AorB to just 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}

在条件 if (isA(x)) 的 then 分支中,x 将具有类型 A

¥In the then-branch of the conditional if (isA(x)), x will have the type A.

单边类型保护

¥One-sided Type Guards

注意:当在 flowconfig 中设置选项 one_sided_type_guards=true 时,此功能从 v0.237.0 开始可用。从 v0.239.0 开始,它默认启用。

¥Note: This feature is available as of v0.237.0 when option one_sided_type_guards=true is set in the flowconfig. It is enabled by default as of v0.239.0.

在某些情况下,我们可能希望声明类型保护函数仅细化条件的 then 分支。例如考虑函数

¥In some cases we may want to declare that a type guard function only refines the then-branch of a conditional. Consider for example the function

1function isPositive(n: ?number): boolean {2  return n != null && n > 0;3}

如果我们将 n is number 声明为此函数的类型保护,则在以下代码中:

¥If we declared n is number as the type guard of this function then in the following code:

declare n: ?number;
if (isPositive(n)) {
// n is number here
} else {
// n would be null | void here
}

我们将能够在 else 分支中确定 nnull | void。然而,这并不正确,因为 n 可能只是一个非负数。

¥we would be able to establish that n is null | void in the else-branch. This is not true, however, since n could just be a non-negative number.

单侧类型保护(我们将其注释为 implies param is PredicateType)让我们指定谓词仅在肯定情况下缩小类型。例如,

¥One-sided type guards, which we annotate as implies param is PredicateType, let us specify that a predicate narrows the type in only the positive case. For example,

1function isPositive(n: ?number): implies n is number {2  return n != null && n > 0;3}

现在,我们将得到以下行为

¥Now, we'll get the following behavior

declare n: ?number;
if (isPositive(n)) {
// n is number here
} else {
// n is still ?number
}

使用 Array.filter 进行优化

¥Refine with Array.filter

当你使用包含 (value: T) => value is S 类型的类型保护的回调函数对 Array<T> 类型的数组调用 filter 时,Flow 会进行识别。它将使用它来生成 Array<S> 类型的输出数组。请注意,S 必须是数组元素 T 类型的子类型。

¥Flow recognizes when you call filter on an array of type Array<T> with a callback function that holds a type guard with type (value: T) => value is S. It will use this to produce an output array of type Array<S>. Note that S needs to be a subtype of the type of the array element T.

例如

¥For example

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}

filterError1 中,过滤生成与预期返回类型 Array<Error> 不兼容的 Array<Success>

¥In filterError1, filtering produces Array<Success> that is not compatible with the expected return type Array<Error>.

filterError2 中,谓词 response.type === 'success' 用于将 Response 细化为 Success,而不是 Error

¥In filterError2, the predicate response.type === 'success' is used to refine Responses to Successs, not Errors.

定义类型保护函数

¥Defining Type Guard Functions

为了确保类型保护函数的细化是合理的,Flow 运行许多与这些函数相关的检查。

¥To ensure that refinement with type guard functions is sound, Flow runs a number of checks associated with these functions.

谓词参数是函数的常规参数

¥Predicate parameter is a regular parameter to the function

parameter is Type 形式的类型保护注释中,parameter 需要属于当前函数的参数列表。

¥In a type guard annotation of the form parameter is Type, parameter needs to belong to the current function's parameter list.

1function missing(param: mixed): prop is number {2  return typeof param === "number";3}

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

¥It cannot be a parameter bound in a destructuring pattern, or a rest paramter:

1function destructuring({prop}: {prop: mixed}): prop is number {2  return typeof prop === "number";3}
1function rest(...value: Array<mixed>): value is Array<mixed> {2  return Array.isArray(value);3}

谓词类型与参数类型一致

¥Predicate type is consistent with the parameter type

类型保护 Type 需要与参数的类型兼容。换句话说,给定一个定义

¥The type guard Type needs to be compatible with the type of the parameter. In other words, given a definition

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

Flow 将检查 Type 是否是 ParamType 的子类型。所以下面的语句会报错:

¥Flow will check that Type is a subtype of ParamType. So the following will be an error:

1function isNumber(x: string): x is number {2  return typeof x === "number";3}

类型保护函数返回布尔值

¥Type guard function returns boolean

类型保护函数需要返回一个布尔表达式。以下为无效声明:

¥A type guard function needs to return a boolean expression. The following are invalid declarations:

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

nonMaybe 的正确版本是

¥A correct version of nonMaybe would be

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

谓词类型与精化类型一致

¥Predicate type is consistent with refined type

除了上述检查之外,Flow 还确保声明的类型保护与函数体内发生的检查一致。为了建立这一点,它需要保证两件事:

¥In addition to the above checks, Flow also ensures that the declared type guard is consistent with the check happening in the body of the function. To establish this it needs to guarantee two things:

  1. 应用返回表达式的谓词后,返回位置处的精炼参数的类型是保护类型的子类型。例如,以下定义是正确的:

    ¥The type of the refined parameter at the return location after the predicate of the return expression has been applied is a subtype of the guard type. For example, the following definitions are correct:

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 中会报错:

¥But in the following Flow will raise errors:

1function numOrStrError(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "boolean");3}
  1. 类型保护函数可用于细化条件的 else 分支。例如,

    ¥Type guard functions can be used to refine the else-branch of conditionals. For example,

1function isNumber(x: mixed): x is number {2  return typeof x === "number";3}4
5declare var value: number | string;6if (isNumber(value)) {7  value as number; // okay8} else {9  value as string; // also okay10}

因此,第一个要求的逆形式也需要保持。具体来说,如果我们否定函数中编码的谓词,并使用它来细化输入,则结果根本不能与类型保护重叠。此条件相当于检查使用函数谓词否定细化的类型保护是否是 empty 的子类型。例如,以下内容会引发错误:

¥Therefore, the inverse form of the first requirement also needs to hold. Specifically, if we negate the predicate encoded in the function, and use it to refine the input, then the result must not overlap with the type guard at all. This condition is equivalent to checking that the type guard refined with the negation of the function predicate is a subtype of empty. For example the following raises an error:

1function isPosNum(x: mixed): x is number {2    return typeof x === 'number' && x > 0;3}

这是因为 isPosNum 谓词的否定是“x 不是数字或 x<=0”。此谓词相当于空谓词,不会细化它所应用的输入类型。

¥This is because the negation of the predicate of isPosNum is "x is not a number or x<=0". This predicate is equivalent to the empty predicate and does not refine the input type it is applied to.

如果你看到与此检查相关的错误,请考虑使用单侧类型保护(写入 implies x is T)。单侧类型保护不需要此检查,因为它们不会细化条件的 else 分支。

¥If you're seeing errors related to this check, consider using a one-sided type guard (write implies x is T). Ones-sided type guards do not require this check, since they do not refine the else-branch of conditionals.

注意:仅当在 flowconfig 中设置 one_sided_type_guards=true 时,才会进行此检查。从 v0.239.0 开始,它默认发生。

¥Note: This check only happens when one_sided_type_guards=true is set in the flowconfig. It happens by default as of v0.239.0.

  1. 精炼后的参数不能在类型保护函数体内重新赋值。因此以下是错误的:

    ¥The parameter that is refined cannot be reassigned in the body of the type guard function. Therefore the following are errors:

1function isNumberError1(x: mixed): x is number {2  x = 1;3  return typeof x === "number";4}
1function isNumberError2(x: mixed): x is number {2  function foo() {3    x = 1;4  }5  foo();6  return typeof x === "number";7}

采纳

¥Adoption

要使用类型保护,你需要升级基础设施以使其支持语法:

¥To use type guards, you need to upgrade your infrastructure so that it supports the syntax:

  • flowflow-parser:0.209.1.在 v0.209.1 到 v0.211.1 之间,你需要在 .flowconfig 中显式启用它,在 [options] 标题下添加 type_guards=true。从 0.237.0 版本开始,可以使用选项 one_sided_type_guards=true 使用单边类型保护,从 v0.239.0 开始,它默认启用。

    ¥flow and flow-parser: 0.209.1. Between v0.209.1 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the [options] heading, add type_guards=true. One-sided type guards are available as of version 0.237.0 with the option one_sided_type_guards=true, and are enabled by default as of v0.239.0.

  • prettier:3

  • 使用 babel-plugin-syntax-hermes-parser babel。请参阅 我们的 Babel 指南 了解设置说明。

    ¥babel with babel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.

  • 使用 hermes-eslint eslint。请参阅 我们的 ESLint 指南 了解设置说明。

    ¥eslint with hermes-eslint. See our ESLint guide for setup instructions.