typescript - 如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型?
问题描述
使用排除运算符不起作用。
type test = Exclude<'a'|'b'|string, string>
// produces type test = never
我可以理解为什么“字符串除外”也意味着排除所有字符串文字,但是我怎样才能得到'a'|'b'
呢'a'|'b'|string
?
如果需要,假设是最新的 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>
可以理解的是,这失败并给出了一个空类型。
如果你正在阅读这篇文章,但我被公共汽车撞了,这个问题的答案可能在这个 GitHub 线程中找到。
解决方案
当前解决方案(Typescript 4.1
+)
2021 年编辑:的2.8
实现自 Typescript 以来就KnownKeys<T>
被破坏了,但是使用键重新映射的新的、更具语义的实现是可用的,因为:4.3.1-rc
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 版本的保留解决方案:
我在这个 GitHub 线程中从@ferdaber那里得到了一个解决方案。
编辑:事实证明,它是由@ajafff 于 1986 年出版的,有点夸张
该解决方案需要 TypeScript 2.8 的条件类型,如下所示:
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
extends string
(就像'a'
extends一样string
)但string
不 extend的事实,'a'
对于数字也是如此。基本上,我们必须将其extends
视为“进入”
首先,它创建一个映射类型,其中对于 T 的每个键,其值为:
- 如果字符串扩展键(键是字符串,而不是子类型)=> 从不
- 如果数字扩展键(键是数字,而不是子类型)=> 从不
- 否则,实际的字符串键
然后,它本质上是 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!
GitHub线程中的解释以防有人在那里提出异议
推荐阅读
- windows-installer - 使用 MSI 编辑器(例如 MS Orca)配置 Cisco Jabber
- c++ - 解决 Google Kickstart 2018 轮 c Q3
- c# - 防止浏览器启动超过 1 次
- python - Python - 更新字典列表中的值
- c++ - 为什么在以下代码中调用了两次复制构造函数和析构函数
- python - Python字典,寻找具体方法
- ios - xcode ios构建链接器命令失败,退出代码
- node.js - 服务器上通过 Http 请求的 URL 必须是绝对的
- angular - 角元素错误。“未能构造 'HTMLElement ...'
- html - 文本对齐问题