在具有索引类型签名的类上使用Omit类型会导致不需要最少的属性

时间:2019-02-22 11:23:24

标签: typescript

我有一个具有一些强制性属性的类,然后可以通过扩展该类的类来定义其他属性(或者,无论如何,有些我不知道的额外属性)。

现在我正尝试在其上使用Omit类型,定义为

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

如果我们将类定义为

class Thing {
  constructor(
     public id: number,
     public name: string,
     public description: string
  ) {}
}

我们可以进行Omit<Thing, 'id'>并获得正确的类型推断,例如Pick<Thing, "name" | "description">

如果我们在类中添加[index: string]: any以支持未知属性并重试Omit<Thing, 'id'>,则推断的类型将为Pick<Thing, string | number>

class Thing {
  constructor(
     public id: number,
     public name: string,
     public description: string
  ) {}

  [index: string]: any;
}

这实际上没有任何意义:当我使用该类键入内容时,即使定义了[index: string]: any;,所有属性也是必需的,为什么在省略一个类型时却变成了更宽松的类型的性质?我希望返回类型仍然是Pick<Thing, "name" | "description">

有人知道发生了什么吗?

2 个答案:

答案 0 :(得分:2)

您可以制作一个Omit<>来处理索引签名,但是确实很难看。

如前所述,如果keyof T具有字符串索引签名,则Tstring | number,并且所有文字键(如'id')都会被占用。通过执行一些crazy type-system hoop jumping,可以获得带有索引签名的类型的文字键。一旦有了文字键和索引签名键,就可以建立一个Omit

type _LiteralKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
type LiteralKeys<T> = _LiteralKeys<T> extends infer K ?
  [K] extends [keyof T] ? K : never : never;

type Lookup<T, K> = K extends keyof T ? T[K] : never;
type _IndexKey<T> = string extends keyof T ?
  Lookup<T, string> extends Lookup<T, number> ?
  string : (string | number) : number extends keyof T ? number : never;
type IndexKey<T> = _IndexKey<T> extends infer K ?
  [K] extends [keyof T] ? K : never : never;

type WidenStringIndex<T extends keyof any> =
  string extends T ? (string | number) : T;

type Omit<T, K extends keyof T,
  O = Pick<T, Exclude<
    LiteralKeys<T>, K>> & Pick<T, Exclude<IndexKey<T>, WidenStringIndex<K>>>
  > = { [P in keyof O]: O[P] }

我说这很丑。

您可以验证其行为是否符合预期:

type RemoveId = Omit<Thing, 'id'>;
// type RemoveId = {
//  [x: string]: any;
//  name: string;
//  description: string;
// }

如果您想使用以上Omit,请将其推送到没有人看的某个库中,并在您的代码中使用它;这取决于你。我不知道它可以处理所有可能的极端情况,甚至不能处理某些情况下的“正确”行为(例如,您可以同时拥有stringnumber索引,其中{{ 1}}的值比number的值要窄...如果您想string使用这种类型,您想知道什么?‍♂️)因此,风险自负。但我只想指出,可能有一种 解决方案。

好的,希望能有所帮助。祝你好运!

答案 1 :(得分:0)

Omit使用keyof获取类型的所有键,然后从中排除省略键。因此Omit的定义可能是:

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

这种方法的问题(不是另一种方法)是,对于具有索引签名keyof T的类型,总是只返回string | number。这是合乎逻辑的,并且是唯一可能的方式。例如,Thing可能有哪些键?好吧,它们分别是idnamedescription,也可以是任何string,因此keyof可能会返回所有这些id | name | description | string | number的并集。但是string是所有字符串文字类型的基本类型。这意味着string将耗尽所有其他字符串文字类型,从而产生string

鉴于keyof Thingstring,从string(带有Exclude<keyof T, K>)中排除任何内容,由于您发现令人惊讶的签名,仍将导致string