跳至主要内容

条件类型

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

基本用法

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

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

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

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'

泛型条件类型

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

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'

依赖于输入类型的函数返回类型

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

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' }

上面的示例也可以用函数重载来编写

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' }

在条件类型中推断

您可以使用条件类型的强大功能来使用 infer 类型提取类型的部分。例如,内置的 ReturnType 由条件类型提供支持

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 自动确定其类型。

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

在执行上述示例之类的提取时,您通常希望条件类型始终选择成功提取类型的真分支。例如,默默地选择假分支不是很好

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

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

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>;
5:17-5:22: Cannot instantiate `ReturnType` because string [1] is incompatible with function type [2] in type argument `T`. [incompatible-type-arg]

分布式条件类型

当泛型条件类型被赋予联合类型作为类型参数时,条件类型会分布到联合类型的成员上。例如,上面的 TypeOf 示例可以分布到联合类型上

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'

这是通过首先分解联合类型,然后将每个类型传递给条件类型以分别进行评估来实现的。在上面的示例中,这看起来像

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

如果您想避免这种行为,您可以将检查类型和扩展类型用一元元组类型包装起来

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] 的子类型,因为元组是不变地类型的。

采用

要使用条件类型,您需要升级您的基础设施,使其支持语法

  • flowflow-parser:0.208.0。在 v0.208 到 v0.211.1 之间,您需要在您的 .flowconfig 中显式启用它,在 [options] 标题下,添加 conditional_type=true
  • prettier: 3
  • babelbabel-plugin-syntax-hermes-parser。有关设置说明,请参阅我们的 Babel 指南
  • eslinthermes-eslint。有关设置说明,请参阅我们的 ESLint 指南