如何在不删除 TypeScript 中的子类型的情况下从联合类型中删除更广泛的类型? [英] How can I remove a wider type from a union type without removing its subtypes in TypeScript?

查看:27
本文介绍了如何在不删除 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此 GitHub 线程中.

事实证明,它是,@ajafff​​

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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆