https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
通常情况下,分布性是所期望的行为。为了避免这种行为,你可以用方括号包围extends关键字的每一边。
distributive conditional types
通常是期望的行为吗?有哪些用例?
这是因为对 联合 的分布性往往是类型操作的最自然行为。
假设你有一个类似于
的函数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>
。
但是比较 fab
和 fafb
;对于 f()
的任何合理的实现,你会预测 fab
和 fafb
的可能值完全相同,对吗? fab
是 f(a)
或 f(b)
的结果,因为输入是 a
或 b
,而 fafb
也是 f(a)
或 f(b)
的结果。 这意味着类型 F<A | B>
和类型 F<A> | F<B>
s 应该是同一类型 。
这正是类型函数在联盟上分布的意思。 当且仅当 F<T | U>
对于所有类型 T
和 U
都等价于 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
在这一点上,你可能想知道哪些类型操作不应该 在联合体上分布。 这个概念与 变量 密切相关(更多信息见 本问答 )。 属于 协变 的类型函数,比如那些代表属性读取或数据输出的函数,应该在联合体上分布。 类型函数是 不变的 ,比如那些代表属性写入或数据输入的,也应该以一种方式分布,但是把联合变成 交集 。 但是很多时候你会得到 不变 类型的函数,比如那些代表任意操作的函数,在这些情况下,你不希望在联合体上分布。 因此,能够关闭分布性有时是很有用的。 不过,我不会在这里赘述这个问题,展示一些例子。
[T] extends [U]?
可以关闭分布性?这是硬编码语法? T extends ...
是分布式的,如果 T
是一个通用类型参数。 但是 [T] extends ...
或 {a: T} extends ...
或 AnythingElse<T> extends ...
不是分布式的,因为这些不是通用类型参数。 参见 s tackoverflow.com/questions/55382306/...