如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型? [英] How can I remove a wider type from a union type without removing its subtypes in TypeScript?
问题描述
使用排除运算符不起作用.
Using the Exclude operator doesn't work.
type test = Exclude<'a'|'b'|string, string>
// produces type test = never
我可以理解为什么除了字符串"也意味着排除所有字符串文字,但是我如何从 'a'|'b' 中获取
?'a'|'b'
|string
I can understand why "except strings" also means excluding all the string literals, but how can I obtain 'a'|'b'
out of 'a'|'b'|string
?
如果需要,请使用最新的 TypeScript.
If needed, assume latest TypeScript.
用例如下:
假设第三方库定义了这种类型:
Say a third party library defines this type:
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
}
现在,我想做的是获取 KNOWN 属性的并集:
Now, what I want to do, is get a union of the KNOWN properties:
type KnownProperties = Exclude<keyof JSONSchema4, string|number>
有点可以理解,这会失败并给出一个空类型.
Somewhat understandably, this fails and gives an empty type.
如果您正在阅读本文但我被公共汽车撞了,则可以在 这个 GitHub 线程.
If you are reading this but I was hit by a bus, the answer to this might be found in this GitHub thread.
推荐答案
我从 @ferdaber那里得到了一个解决方案strong> 在 此 GitHub 线程中.
Turns out it was, to little fanfare, published in 1986 by @ajafff
解决方案需要 TypeScript 2.8 的 条件类型,如下所示:
The solution requires TypeScript 2.8's Conditional Types and goes as follows:
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'
extends string
) 但 string
不扩展 'a'
,对于数字也是如此.基本上,我们必须将 extends
视为进入"
The solution is based on the fact that string
extends string
(just as 'a'
extends string
) but string
doesn't extend 'a'
, and similarly for numbers.
Basically, we must think of extends
as "goes into"
首先它创建一个映射类型,其中对于 T 的每个键,其值是:
First it creates a mapped type, where for every key of T, the value is:
- 如果字符串扩展了键(键是字符串,不是子类型)=>从不
- 如果数字扩展键(键是数字,不是子类型)=>从不
- 否则,实际的字符串键
然后,它本质上是 valueof 以获得所有值的联合:
Then, it does essentially valueof to get a union of all the values:
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!
GitHub 线程中的说明,以防有人在那里提出异议
Explaination in GitHub thread in case someone makes an objection over there
这篇关于如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!