【问题标题】:How can I remove a wider type from a union type without removing its subtypes in TypeScript?如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型?
【发布时间】:2019-01-27 23:44:14
【问题描述】:

使用排除运算符不起作用。

type test = Exclude<'a'|'b'|string, string>
// produces type test = never

我可以理解为什么“字符串除外”也意味着排除所有字符串文字,但是如何从'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 中找到。

【问题讨论】:

  • 'a'|'b'|string 简化为 string,然后您才能对其进行任何操作。您必须更改生成此类型的任何代码。那个代码是什么?
  • @MattMcCutchen 联合是接口上keyof 的结果。我现在正在编辑问题。
  • 问题的症结在于删除索引签名。在搞砸了几分钟后,我无法找到解决办法。在this question,jcalz 声称无法做到,我倾向于相信他。
  • @MattMcCutchen 我找到了一种方法,我将其添加为答案。
  • 太棒了......有人去另一个线程并回答它或在此处链接它。

标签: typescript typescript2.8


【解决方案1】:

当前解决方案(Typescript 4.1+)

2021 编辑: KnownKeys&lt;T&gt;2.8 实现是 broken since Typescript 4.3.1-rc,但使用 key remapping 的新的、更语义化的实现自 @987654333 起可用@:

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!

以下是 pre-4.1 Typescript 版本的保留解决方案:


我在this GitHub thread 中从@ferdaber 获得了解决方案。

编辑: 原来是published in 1986@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 的每个键,其值为:

  • 如果字符串扩展键(键是字符串,而不是子类型)=> 从不
  • 如果数字扩展键(键是数字,而不是子类型)=> 从不
  • 否则,实际的字符串键

然后,它本质上是 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以防有人在那边提出异议

【讨论】:

  • 哇!这很聪明。
  • 答案太棒了!!我认为接口 test.opt 应该是可选的,因为类型 a 是从 FirstHalf 生成的,并且 a.opt 的值是 undefined
  • @ZiZiZheng 感谢您发现,已编辑
  • 这里没有工作:const keysToSync = ['apitoken', 'userId', 'active-trade-account'] as const; type KnownKeys&lt;T&gt; = { [K in keyof T]: string extends K ? never : number extends K ? never : K } extends { [_ in keyof T]: infer U } ? U : never; type KeysToSync = KnownKeys&lt;keyof typeof keysToSync&gt;;
  • @DevinRhode KnownKeys 适用于键控集合,而不是裸联合。 KnownKeys&lt;typeof keysToSync&gt; 正确生成 "0" | "1" | "2"。 (是的,不幸的是它们是字符串而不是数字,但这就是 JS 索引语义的真相)。值得注意的是,KnownKeys 在这里很有用,因为它从类型中删除了 number |
【解决方案2】:

每个接受的答案:https://stackoverflow.com/a/51955852/714179。在 TS 4.3.2 这工作:

export type KnownKeys<T> = keyof {
  [K in keyof T as string extends K ? never : number extends K ? never : K]: never
}

【讨论】:

  • 我没有包含stackoverflow.com/a/51956054/2115619,因为这个问题被标记为typescript 2.8,并且Typescript 4.1中引入了Key Remapping
  • @MihailMalostanidis 您可能应该编辑它(附上关于版本的注释和指向ms/TS#44143 的链接),因为人们不太可能继续咨询 TS2.8 的此 Q/A。跨度>
  • 你是怎么做那个链接的:o
  • @jcalz 完成了,我想
猜你喜欢
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
相关资源
最近更新 更多