TypeScript `extends` 条件类型语句仅在使用泛型表达时才有效?
TypeScript `extends` conditional type statement only works if expressed using Generics?
我正在努力更好地理解 TypeScript 中的 extends
关键字及其潜在应用。
我遇到的一件事是两个内置实用程序,Extract
和 Exclude
,它们同时利用了 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;
我一直在努力更好地理解这种“缩小范围”,或者更好地说“子集过滤”是如何工作的,并且尝试创建我自己的实现只是为了看看它的实际效果,并且遇到了这个非常奇怪的问题行为:
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;
为什么会这样?
我希望两个实例都return类型的正确子集,但条件类型只有在通过泛型表达时才有效。
谁能给我解释一下这背后的逻辑?
第二段代码正在执行一次检查,以查看整个类型是否从 number 扩展而来。如果是,它 returns 整个类型,否则它 returns never
。具有泛型的版本将逐步遍历联合中的所有单个类型(首先是 string
,然后是 "lol"
,然后是 0
等)并单独评估它们。然后你会得到一个存活下来的个体类型的联合。
可以从第二个示例中得到一个非 never 值,但前提是每个可能的值都是数字。例如:
type Example = 1 | 3 | 5;
type Example2 = Example extends number ? Example : never;
// Example2 is 1 | 3 | 5
请参阅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.
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
比其他人更优雅。
我正在努力更好地理解 TypeScript 中的 extends
关键字及其潜在应用。
我遇到的一件事是两个内置实用程序,Extract
和 Exclude
,它们同时利用了 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;
我一直在努力更好地理解这种“缩小范围”,或者更好地说“子集过滤”是如何工作的,并且尝试创建我自己的实现只是为了看看它的实际效果,并且遇到了这个非常奇怪的问题行为:
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;
为什么会这样?
我希望两个实例都return类型的正确子集,但条件类型只有在通过泛型表达时才有效。
谁能给我解释一下这背后的逻辑?
第二段代码正在执行一次检查,以查看整个类型是否从 number 扩展而来。如果是,它 returns 整个类型,否则它 returns never
。具有泛型的版本将逐步遍历联合中的所有单个类型(首先是 string
,然后是 "lol"
,然后是 0
等)并单独评估它们。然后你会得到一个存活下来的个体类型的联合。
可以从第二个示例中得到一个非 never 值,但前提是每个可能的值都是数字。例如:
type Example = 1 | 3 | 5;
type Example2 = Example extends number ? Example : never;
// Example2 is 1 | 3 | 5
请参阅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.
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
比其他人更优雅。