简介
该特性直白来说:若在条件类型中给定的泛型为联合类型,则会将联合类型的每个成员分别执行条件运算。理解起来并不难,官方文档给了一个简单的示例:
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
// 等价于执行了 (string extends any ? string[] : never) | (number extends any ? number[] : never)
另外,在文档的最后,还给出了如何禁用该特性的方法:
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;
常见应用
- IsNever
实现一个类型工具IsNever<T>
若 T 为 never 则返回 true 否则返回 false。若按普通思路这样写会发现达不到预期:
type isNever<T> = T extends never ? true : false;
type Test = isNever<never> // false
原因在于 never 会被当做联合类型进行分发,但是 never 会被当做一个没有任何成员的联合类型,也就没法分发了。按上面提到的方式禁用分发即可:
type isNever<T> = [T] extends [never] ? true : false;
type Test = isNever<never> // true
当然,除了官方提供的阻止分发的方式,还可以衍生出其它原理类似的方式,都是对原泛型进行 wrap:
type isNever1<T> = (() => T) extends (() => never) ? true : false;
// 注意: T 与 never 的位置与上面是不一样的,这涉及到函数参数的逆变
type IsNever2<T> = ((a: never) => void) extends ((a: T) => void) ? true : false;
type isNever3<T> = { a: T } extends { a: never } ? true : false;
type isNever4<T> = T[] extends never[] ? true : false;
// ...
- OptionalKeys
实现一个类型工具OptionalKeys<T>
获取 Record T 中所有 optional key,例如:
type Test = OptionalKeys<{
a?: 1,
b: 2,
c?: 3,
}> // 'a' | 'c'
利用分发的特性,我们可以这么实现:
type OptionalKeys<T extends Record<string, any>,K = keyof T> =
// 分发所有的 key
K extends keyof T ?
// 逐一判断是否符合条件
T extends Required<Pick<T,K>> ?
never
: K
: never;
借助同样的套路可以得到类似的其他几个工具函数:ReadonlyKeys<T>
、RequiredKeys<T>
:
// Equal1<X, Y> from https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650
type Equal1<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<U>() => U extends Y ? 1 : 2) ? true : false;
type ReadOnlydKeys<T extends Record<string, any>,K = keyof T> = K extends keyof T ?
Equal<Pick<T, K>, Readonly<Pick<T, K>>> extends true ? K : never
:never;
type RequiredKeys<T extends Record<string, any>,K = keyof T> = K extends keyof T ?
T extends Required<Pick<T,K>> ? K : never
:never;
当然,对于本例,自行遍历是可行的:
type OptionalKeys<T extends Record<string, any>> = keyof {
[key in keyof T as T extends Required<Pick<T, key>> ? never : key]: 1
}