Skip to main content

条件类型

Flow 的条件类型允许你通过检查输入类型来有条件地在两种不同的输出类型之间进行选择。它对于提取类型的部分或描述复杂的重载很有用。

¥Flow's conditional type allows you to conditionally choose between two different output types by inspecting an input type. It is useful to extract parts of a type, or to describe a complex overload.

基本用法

¥Basic Usage

它的语法类似于条件表达式:CheckType extends ExtendsType ? TrueType : FalseType

¥It has a syntax that is similar to conditional expressions: CheckType extends ExtendsType ? TrueType : FalseType.

如果 CheckTypeExtendsType 的子类型,则条件类型将计算为 TrueType。否则,将被评估为 FalseType

¥If CheckType is a subtype of ExtendsType, the conditional type will be evaluated to TrueType. Otherwise, it will be evaluated to FalseType.

以下示例说明了这两种情况。

¥The following example illustrates both cases.

1class Animal {}2class Dog extends Animal {}3
4type TypeofAnimal = Dog extends Animal ? 'animal' : 'unknown'; // evaluates to 'animal'5type TypeofString = string extends Animal ? 'animal' : 'unknown'; // evaluates to 'unknown'

通用条件类型

¥Generic Conditional Types

这可能看起来不太有用,因为你已经知道它将评估为什么类型。但是,与泛型结合,你可以对类型执行复杂的计算。例如,你可以写下类型级 typeof 运算符:

¥This might not look very useful, since you already know what type it will evaluate to. However, combining with generics, you can perform complex computations over types. For example, you can write down a type-level typeof operator:

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' :5  T extends number ? 'number' :6  T extends boolean ? 'boolean' :7  T extends (...$ReadOnlyArray<empty>)=>mixed ? 'function' : 'object'8
9type T1 = TypeOf<null>; // evaluates to 'null'10type T2 = TypeOf<void>; // evaluates to 'undefined'11type T3 = TypeOf<string>; // evaluates to 'string'12type T4 = TypeOf<number>; // evaluates to 'number'13type T5 = TypeOf<boolean>; // evaluates to 'boolean'14type T6 = TypeOf<(string)=>boolean>; // evaluates to 'function'15type T7 = TypeOf<{foo: string}>; // evaluates to 'object'

函数返回类型取决于输入类型

¥Function return types dependent on input types

条件类型还允许你直观地描述选择不同函数重载的条件:

¥Conditional types also allow you to intuitively describe the conditions for choosing different function overloads:

1declare function wrap<T>(value: T): T extends string ? { type: 'string', value: string }2                                  : T extends number ? { type: 'number', value: number }3                                  : { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }

上面的例子也可以用函数重载来写:

¥The above example can also be written with function overload:

1declare function wrap(value: string): { type: 'string', value: string }2declare function wrap(value: number): { type: 'number', value: number }3declare function wrap(value: mixed): { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }

在条件类型中进行推断

¥Inferring Within Conditional Types

你可以利用条件类型的强大功能,通过 infer 类型提取类型的部分内容。例如,内置 ReturnType 由条件类型提供支持:

¥You can use the power of conditional types to extract parts of a type using infer types. For example, the builtin ReturnType is powered by conditional types:

1type ReturnType<T> = T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : empty;2
3type N = ReturnType<(string) => number>; // evaluates to `number`4type S = ReturnType<(number) => string>; // evaluates to `string`

我们这里使用推断类型来引入一个新的泛型类型变量,名为 Return,它可以在条件类型的类型分支中使用。推断类型只能出现在条件类型中 extends 子句的右侧。Flow 将在检查类型和扩展类型之间执行子类型检查,以根据输入类型 T 自动找出其类型。

¥We used the infer type here to introduce a new generic type variable named Return, which can be used in the type branch of the conditional type. Infer types can only appear on the right hand side of the extends clause in conditional types. Flow will perform a subtyping check between the check type and the extends type to automatically figure out its type based on the input type T.

type N = ReturnType<(string) => number> 的示例中,Flow 检查 (string) => number 是否是 (...args: $ReadOnlyArray<empty>) => infer Return 的子类型,在此过程中 Return 被约束为 number

¥In the example of type N = ReturnType<(string) => number>, Flow checks if (string) => number is a subtype of (...args: $ReadOnlyArray<empty>) => infer Return, and during this process Return is constrained to number.

在进行像上面的示例这样的提取时,你通常希望条件类型始终选择成功提取类型的真实分支。例如,默默地选择错误分支并不好:

¥When doing extractions like the above example, you usually want the conditional type to always choose the true branch where the type is successfully extracted. For example, silently choosing the false branch is not great:

1type ExtractReturnTypeNoValidation<T> =2  T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : any;3
41 as ExtractReturnTypeNoValidation<string>; // no error :(

相反,你可能希望 Flow 在输入不是函数类型时出错。这可以通过向类型参数添加约束来完成:

¥Instead, you might want Flow to error when the input is not a function type. This can be accomplished by adding constraints to the type parameter:

1type ReturnType<T: (...args: $ReadOnlyArray<empty>) => mixed> =2  T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : any;3
41 as ReturnType<(string) => number>;51 as ReturnType<string>;

分配条件类型

¥Distributive Conditional Types

当为泛型条件类型提供联合类型作为类型参数时,条件将分布在联合的成员上。例如,上面的 TypeOf 示例可以通过联合进行分配:

¥When a generic conditional type is given a union type as a type argument, the conditional distributes over the union's members. For example, the TypeOf example above can distribute over a union:

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' : 'other';5
6type StringOrNull = TypeOf<string | null>; // evaluates to 'string' | 'null'

其工作原理是首先分解联合类型,然后将每个类型传递给要单独评估的条件类型。在上面的示例中,这看起来像:

¥This works by first breaking up the union type, and then passing each type to the conditional type to be evaluated separately. In the example above, this looks something like:

TypeOf<string | null>
--> (break up the union) --> TypeOf<string> | TypeOf<null>
--> (evaluate each conditional type separately) --> 'string' | 'null'

如果你想避免这种行为,你可以用一元元组类型封装检查类型和扩展类型:

¥If you want to avoid this behavior, you can wrap the check type and extends type with unary tuple type:

1type NonDistributiveTypeOf<T> =2  [T] extends [null] ? 'null' :3  [T] extends [void] ? 'undefined' :4  [T] extends [string] ? 'string' : 'other';5
6type Other = NonDistributiveTypeOf<string | null>; // evaluates to 'other'

这个技巧之所以有效,是因为如果检查类型是泛型类型,Flow 将仅启用条件类型的分配行为。上面的示例没有选择条件类型的任何真分支,因为 [string | null] 不是 [null][void][string] 的子类型,因为元组是 不变地 类型的。

¥This trick works because Flow will only enable the distributive behavior of conditional type if the check type is a generic type. The example above does not choose any true branch of the conditional type, because [string | null] is not a subtype of [null], [void], or [string], since tuples are invariantly typed.

采纳

¥Adoption

要使用条件类型,你需要升级基础架构以使其支持语法:

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

  • flowflow-parser:0.208.0.在 v0.208 到 v0.211.1 之间,你需要在 .flowconfig 中显式启用它,在 [options] 标题下添加 conditional_type=true

    ¥flow and flow-parser: 0.208.0. Between v0.208 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the [options] heading, add conditional_type=true.

  • 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.