简体   繁体   English

TypeScript `extends` 条件类型语句仅在使用泛型表达时才有效?

[英]TypeScript `extends` conditional type statement only works if expressed using Generics?

I am trying to gain a better understanding of the extends keyword in TypeScript and its potential applications.我试图更好地理解 TypeScript 中的extends关键字及其潜在应用。

One thing I have come across are two built-in utilities, Extract and Exclude that leverage both extends and Conditional Typing.我遇到的一件事是两个内置实用程序,即ExtractExclude ,它们同时利用了extends和条件类型。

/**
 * 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;

I was playing around to better understand how this "narrowing down", or to better say "subset filtering" works, and have tried creating my own implementation just to see it in action, and have come across this really odd behaviour:我正在玩弄以更好地理解这种“缩小”是如何工作的,或者更好地说明“子集过滤”的工作原理,并尝试创建我自己的实现只是为了查看它的实际效果,并且遇到了这种非常奇怪的行为:

Link to the Playground Example: 链接到游乐场示例:

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;

Why does that happen?为什么会这样?

I would expect both instances to return the correct subset of the type, but the conditional typing only works if express through Generics.我希望两个实例都能返回正确的类型子集,但条件类型仅在通过泛型表达时才有效。

Can someone explain me the logic behind this?有人可以向我解释这背后的逻辑吗?

That second piece of code is doing a single check to see whether the entire type extends from number.第二段代码进行了一次检查,以查看整个类型是否从 number 扩展。 If it does, it returns the entire type, otherwise it returns never .如果是,则返回整个类型,否则返回never The version with generics is going to step through all the individual types in the union (first string , then "lol" , then 0 etc) and evaluate them individually.带有泛型的版本将遍历联合中的所有单个类型(第一个string ,然后是"lol" ,然后是0等)并单独评估它们。 Then you get a union of whichever individual types survived.然后,您将获得幸存下来的任何个体类型的联合。

It is possible to get a non-never value out of your second example, but only if every possible value is a number.可以从您的第二个示例中获得一个非从不值,但前提是每个可能的值都是一个数字。 For example:例如:

type Example = 1 | 3 | 5;
type Example2 = Example extends number ? Example : never;
//  Example2 is 1 | 3 | 5

Please see distributive-conditional-types :请参阅分配条件类型

When conditional types act on a generic type, they become distributive when given a union type.当条件类型作用于泛型类型时,当给定联合类型时,它们会变成分布式的。 For example, take the following:例如,采取以下措施:

type ToArray<Type> = Type extends any ? Type[] : never;

If we plug a union type into ToArray, then the conditional type will be applied to each member of that union.如果我们将联合类型插入 ToArray,则条件类型将应用于该联合的每个成员。

type ToArray<Type> = Type extends any ? Type[] : never;
 
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

Hence, if you use extends with generic type, whole conditional type applies to each element in the union.因此,如果您使用具有泛型类型的extends ,则整个条件类型适用于联合中的每个元素。

If you use extends with non generic type, like you use in your second example, conditional types applies to the whole type.如果您使用非泛型类型extends ,就像您在第二个示例中使用的那样,条件类型适用于整个类型。

You can even turn off distributivity in your fisrst example.您甚至可以在第一个示例中关闭分配。 Just wrap your generics into a square brackets:只需将您的泛型包装在方括号中:

type ValueSet = string | 'lol' | 0 | {a: 1} | number[] | 643;


type CustomExclude<T, U> = [T] extends [U] ? T : never;

// never
type Result1 =  CustomExclude<ValueSet, number>; 


Generic wrapped into square brackets is treated as a non generic type, just like in your first example.包裹在方括号中的泛型被视为非泛型类型,就像在您的第一个示例中一样。

In practice, this pattern is very useful.在实践中,这种模式非常有用。 It is common to use T extends any just to turn on distributivity.通常使用T extends any来打开分配性。

Assume you have some object type.假设您有一些对象类型。 You want to get all keys and apply some modificator to them.您想获取所有密钥并对其应用一些修改器。 In other words map them.换句话说,映射它们。 COnsider this example:考虑这个例子:

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>

As you might have noticed, ChangeKey is more elegant than others.您可能已经注意到, ChangeKey比其他的更优雅。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM