如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型?
How can I remove a wider type from a union type without removing its subtypes in TypeScript?
使用排除运算符不起作用。
type test = Exclude<'a'|'b'|string, string>
// produces type test = never
我能理解为什么"except strings"也意味着排除所有的字符串文字,但是我如何从'a'|'b'|string
中获得'a'|'b'
?
如果需要,假设最新的 TypeScript。
用例如下:
假设第三方库定义了这个类型:
export interface JSONSchema4 {
id?: string
$ref?: string
$schema?: string
title?: string
description?: string
default?: JSONSchema4Type
multipleOf?: number
maximum?: number
exclusiveMaximum?: boolean
minimum?: number
exclusiveMinimum?: boolean
maxLength?: number
minLength?: number
pattern?: string
// to allow third party extensions
[k: string]: any
}
现在,我想要做的是获得已知属性的联合:
type KnownProperties = Exclude<keyof JSONSchema4, string|number>
有点可以理解,这失败了,给出了一个空类型。
如果你正在阅读这篇文章,但我被公交车撞了,这个问题的答案可能会在 this GitHub thread 中找到。
当前解决方案(Typescript 4.1
+)
2021 编辑: KnownKeys<T>
的 2.8
实现是 broken since Typescript 4.3.1-rc
, but a new, more semantic implementation using key remapping,因为 4.1
:
type RemoveIndex<T> = {
[ K in keyof T as string extends K ? never : number extends K ? never : K ] : T[K]
};
然后可以如下使用:
type KnownKeys<T> = keyof RemoveIndex<T>;
interface test {
req: string
opt?: string
[k: string]: any
}
type demo = KnownKeys<test>; // "req" | "opt" // Absolutely glorious!
以下是 4.1
之前的 Typescript 版本的保留解决方案:
我从 @ferdaber in this GitHub thread 得到了解决方案。
编辑: 事实证明,published in 1986 by @ajafff
解决方案需要 TypeScript 2.8 的 Conditional Types 并且如下:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
以下是我的解释尝试:
解决方案基于 string
扩展 string
(就像 'a'
扩展 string
)但 string
不扩展 'a'
,数字也类似。
基本上,我们必须将 extends
视为“进入”
首先它创建一个映射类型,其中对于 T 的每个键,值是:
- if string extends key (key is string, not a subtype) => 从不
- if number extends key (key is number, not a subtype) => never
- 否则,实际的字符串键
然后,它基本上执行 valueof 以获得所有值的并集:
type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
或者,更准确地说:
interface test {
req: string
opt?: string
[k: string]: any
}
type FirstHalf<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
}
type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
// or equivalently, since T here, and T in FirstHalf have the same keys,
// we can use T from FirstHalf instead:
type SecondHalf<First, T> = First extends { [_ in keyof T]: infer U } ? U : never;
type a = FirstHalf<test>
//Output:
type a = {
[x: string]: never;
req: "req";
opt?: "opt" | undefined;
}
type a2 = ValuesOf<a> // "req" | "opt" // Success!
type a2b = SecondHalf<a, test> // "req" | "opt" // Success!
// Substituting, to create a single type definition, we get @ferdaber's solution:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
// type b = KnownKeys<test> // "req" | "opt" // Absolutely glorious!
Explaination in GitHub thread万一那边有人反对
每个接受的答案:。在 TS 4.3.2 这有效:
export type KnownKeys<T> = keyof {
[K in keyof T as string extends K ? never : number extends K ? never : K]: never
}
使用排除运算符不起作用。
type test = Exclude<'a'|'b'|string, string>
// produces type test = never
我能理解为什么"except strings"也意味着排除所有的字符串文字,但是我如何从'a'|'b'|string
中获得'a'|'b'
?
如果需要,假设最新的 TypeScript。
用例如下:
假设第三方库定义了这个类型:
export interface JSONSchema4 {
id?: string
$ref?: string
$schema?: string
title?: string
description?: string
default?: JSONSchema4Type
multipleOf?: number
maximum?: number
exclusiveMaximum?: boolean
minimum?: number
exclusiveMinimum?: boolean
maxLength?: number
minLength?: number
pattern?: string
// to allow third party extensions
[k: string]: any
}
现在,我想要做的是获得已知属性的联合:
type KnownProperties = Exclude<keyof JSONSchema4, string|number>
有点可以理解,这失败了,给出了一个空类型。
如果你正在阅读这篇文章,但我被公交车撞了,这个问题的答案可能会在 this GitHub thread 中找到。
当前解决方案(Typescript 4.1
+)
2021 编辑: KnownKeys<T>
的 2.8
实现是 broken since Typescript 4.3.1-rc
, but a new, more semantic implementation using key remapping,因为 4.1
:
type RemoveIndex<T> = {
[ K in keyof T as string extends K ? never : number extends K ? never : K ] : T[K]
};
然后可以如下使用:
type KnownKeys<T> = keyof RemoveIndex<T>;
interface test {
req: string
opt?: string
[k: string]: any
}
type demo = KnownKeys<test>; // "req" | "opt" // Absolutely glorious!
以下是 4.1
之前的 Typescript 版本的保留解决方案:
我从 @ferdaber in this GitHub thread 得到了解决方案。
编辑: 事实证明,published in 1986 by @ajafff
解决方案需要 TypeScript 2.8 的 Conditional Types 并且如下:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
以下是我的解释尝试:
解决方案基于 string
扩展 string
(就像 'a'
扩展 string
)但 string
不扩展 'a'
,数字也类似。
基本上,我们必须将 extends
视为“进入”
首先它创建一个映射类型,其中对于 T 的每个键,值是:
- if string extends key (key is string, not a subtype) => 从不
- if number extends key (key is number, not a subtype) => never
- 否则,实际的字符串键
然后,它基本上执行 valueof 以获得所有值的并集:
type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
或者,更准确地说:
interface test {
req: string
opt?: string
[k: string]: any
}
type FirstHalf<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
}
type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
// or equivalently, since T here, and T in FirstHalf have the same keys,
// we can use T from FirstHalf instead:
type SecondHalf<First, T> = First extends { [_ in keyof T]: infer U } ? U : never;
type a = FirstHalf<test>
//Output:
type a = {
[x: string]: never;
req: "req";
opt?: "opt" | undefined;
}
type a2 = ValuesOf<a> // "req" | "opt" // Success!
type a2b = SecondHalf<a, test> // "req" | "opt" // Success!
// Substituting, to create a single type definition, we get @ferdaber's solution:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
// type b = KnownKeys<test> // "req" | "opt" // Absolutely glorious!
Explaination in GitHub thread万一那边有人反对
每个接受的答案:
export type KnownKeys<T> = keyof {
[K in keyof T as string extends K ? never : number extends K ? never : K]: never
}