我正在努力更好地了解TypeScript中的extends
关键字及其潜在应用程序。
我遇到的一件事是两个内置的实用程序,Extract
和Exclude
,它们同时利用了extends
和Conditional Typeing。
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
我四处玩耍是为了更好地理解这个";"缩小";,或者更好地说";子集滤波";工作,并尝试创建自己的实现,只是为了看到它的实际应用,但遇到了这种非常奇怪的行为:
游乐场链接示例:
type ValueSet = string | 'lol' | 0 | {a: 1} | number[] | 643;
type CustomExclude<T, U> = T extends U ? T : never;
// this works:
// type Result1 = 0 | 643
type Result1 = CustomExclude<ValueSet, number>;
// but this doesn't?
// type Result2 = never
type Result2 = ValueSet extends number ? ValueSet : never;
为什么会发生这种情况?
我希望这两个实例都返回正确的类型子集,但条件类型只有在通过泛型表达时才有效。
有人能给我解释一下这背后的逻辑吗?
请参阅分布式条件类型:
当条件类型作用于泛型类型时,当给定联合类型时,它们将变为分布式类型。例如,以以下内容为例:
type ToArray<Type> = Type extends any ? Type[] : never;
如果我们将联合类型插入ToArray,则条件类型将应用于该联合的每个成员。
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
因此,如果将extends
与泛型类型一起使用,则整个条件类型将应用于并集中的每个元素。
如果将extends
与非泛型类型一起使用,就像在第二个示例中使用的那样,条件类型将应用于整个类型。
您甚至可以在第一个示例中关闭分布性。只需将你的仿制药包装在方括号中:
type ValueSet = string | 'lol' | 0 | {a: 1} | number[] | 643;
type CustomExclude<T, U> = [T] extends [U] ? T : never;
// never
type Result1 = CustomExclude<ValueSet, number>;
包装在方括号中的泛型被视为非泛型类型,就像您的第一个示例中一样。
在实践中,这种模式非常有用。使用T extends any
只是为了打开分发性是很常见的。
假设您有某种对象类型。您希望获得所有密钥并对其应用一些修饰符。换句话说,绘制它们的地图。考虑这个例子:
type Foo = {
name: string;
age: number
}
// non verbose approach, distributivity
type ChangeKey<T> = keyof T extends string ? `${keyof T}-updated` : never
type Result = ChangeKey<Foo>
// middle verbose approach
type ChangeKey1<T> = {
[Prop in keyof T]: Prop extends string ? `${Prop}-updated` : never
}[keyof T]
type Result1 = ChangeKey1<Foo>
// verbose approach
type ChangeKey2<T extends Record<string, unknown>> = keyof {
[Prop in keyof T as Prop extends string ? `${Prop}-updated` : never]: never
}
type Result2 = ChangeKey2<Foo>
正如您可能已经注意到的,ChangeKey
比其他产品更优雅。
第二段代码正在进行一次检查,以查看整个类型是否从数字扩展。如果是,则返回整个类型,否则返回never
。带有泛型的版本将遍历并集中的所有单个类型(首先是string
,然后是"lol"
,然后是0
等),并对它们进行单独评估。然后你得到了一个幸存下来的任何一种类型的联盟。
从第二个例子中可以得到一个非never值,但前提是每个可能的值都是一个数字。例如:
type Example = 1 | 3 | 5;
type Example2 = Example extends number ? Example : never;
// Example2 is 1 | 3 | 5