`分布式条件类型`是典型的理想行为吗?为什么?

Is `distributive conditional types` the desired behavior typically? Why?


问题

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

通常情况下,分布性是所期望的行为。为了避免这种行为,你可以用方括号包围extends关键字的每一边。

distributive conditional types 通常是期望的行为吗?有哪些用例?

答案1

这是因为对 联合 的分布性往往是类型操作的最自然行为。

假设你有一个类似于

的函数
declare function f<T>(x: T): F<T>;

,它接收一个类型为 T 的值并返回一个类型为 F<T> 的值,其中 F 是一些通用类型的函数。 如果你有一个类型为 A 的值 a ,和一个类型为 B 的值 b ,那么 f(a) 将是类型为 F<A>f(b) 将是类型为 F<B>

declare function f<T>(x: T): F<T>;
const fa = f(a); // F<A>
const fb = f(b); // F<B>

现在,当你把 a b 传给 f() 并把结果分配给一个变量时会发生什么?

const fab = f(Math.random() < 0.5 ? a : b); // F<A | B>

输入的类型是 A | B ,所以根据定义,输出必须是 F<A | B> 。 另一方面,如果你 要么 调用 f(a) ,要么 f(b) 并把结果赋给一个变量,会发生什么?

const fafb = Math.random() < 0.5 ? f(a) : f(b); // F<A> | F<B>

那么,根据定义,结果一定是 F<A> | F<B>

但是比较 fabfafb ;对于 f() 的任何合理的实现,你会预测 fabfafb 的可能值完全相同,对吗? fabf(a)f(b) 的结果,因为输入是 ab ,而 fafb 也是 f(a)f(b) 的结果。 这意味着类型 F<A | B> 和类型 F<A> | F<B> s 应该是同一类型

这正是类型函数在联盟上分布的意思。 当且仅当 F<T | U> 对于所有类型 TU 都等价于 F<T> | F<U> 时,一个类型函数 F<> 才会分布在联合体上。


因此,任何用类型操作表示数据转换的用例都可能是你希望类型操作在联合体上是分布的。 让我们来看看TypeScript中一些常见的分布式类型操作。

索引访问类型 是对联合的分布式:

interface I { x: string, y: number };
type IX = I["x"]; // string
type IY = I["y"]; // number
type IXY = I["x" | "y"]; // string | number
type IXIY = I["x"] | I["y"]; // string | number

type Also = ({ a: string } | { a: number })["a"] // string | number

映射的类型 同态的 (你是用 ms/TS#12447 中介绍的 in keyof 进行映射,当时它被称为 同态 ,更多信息见 这个问题/答案 ),它们在联盟上是可分配的:

type HomMap<T> = { [K in keyof T]: (x: T[K]) => void };
type MA = HomMap<{ a: string }>; // type MA = {  a: (x: string) => void; }
type MB = HomMap<{ b: string }>; // type MB = {  b: (x: string) => void; }
type MAB = HomMap<{ a: string } | { b: string }> 
  // type MAB = HomMap<{ a: string; }> | HomMap<{ b: string; }> 
type MAMB = HomMap<{ a: string; }> | HomMap<{ b: string; }> 
  // type MAMB = HomMap<{ a: string; }> | HomMap<{ b: string; }> 

当然, 条件类型 ,其中检查的类型是一个通用类型参数,是可分配的:

type DCT<T> = T extends { a: string } ? number : string;
type DA = DCT<{ a: string }> // number
type DB = DCT<{ b: string }> // string
type DAB = DCT<{ a: string } | { b: string }>
// type DAB = string | number
type DADB = DCT<{ a: string }> | DCT<{ b: string }>
// type DADB = string | number

在这一点上,你可能想知道哪些类型操作不应该 在联合体上分布。 这个概念与 变量 密切相关(更多信息见 本问答 )。 属于 协变 的类型函数,比如那些代表属性读取或数据输出的函数,应该在联合体上分布。 类型函数是 不变的 ,比如那些代表属性写入或数据输入的,也应该以一种方式分布,但是把联合变成 交集 。 但是很多时候你会得到 不变 类型的函数,比如那些代表任意操作的函数,在这些情况下,你不希望在联合体上分布。 因此,能够关闭分布性有时是很有用的。 不过,我不会在这里赘述这个问题,展示一些例子。

Playground链接到代码

谢谢你的耐心回答。所以分配性类型出现在每一个接受联合类型的泛型中,而不仅仅是在有条件的泛型中?
是的,如图所示,联合体上的分布性发生在多个类型操作中。 但不是每个类型函数都是分布式的......例如,如果你阻止它们同构,你可以得到非分布式的映射类型,像 这个
为什么 [T] extends [U]? 可以关闭分布性?这是硬编码语法?
参见 分布式条件类型 的文档;用于开启分布式条件类型的规则是:被检查的类型本身是一个通用类型参数......所以 T extends ... 是分布式的,如果 T 是一个通用类型参数。 但是 [T] extends ...{a: T} extends ...AnythingElse<T> extends ... 不是分布式的,因为这些不是通用类型参数。 参见 s tackoverflow.com/questions/55382306/...