我有一个具有一些强制性属性的类,然后可以通过扩展该类的类来定义其他属性(或者,无论如何,有些我不知道的额外属性)。
现在我正尝试在其上使用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">
。
有人知道发生了什么吗?
答案 0 :(得分:2)
您可以制作一个Omit<>
来处理索引签名,但是确实很难看。
如前所述,如果keyof T
具有字符串索引签名,则T
为string | 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
,请将其推送到没有人看的某个库中,并在您的代码中使用它;这取决于你。我不知道它可以处理所有可能的极端情况,甚至不能处理某些情况下的“正确”行为(例如,您可以同时拥有string
和number
索引,其中{{ 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
可能有哪些键?好吧,它们分别是id
,name
和description
,也可以是任何string
,因此keyof可能会返回所有这些id | name | description | string | number
的并集。但是string
是所有字符串文字类型的基本类型。这意味着string
将耗尽所有其他字符串文字类型,从而产生string
。
鉴于keyof Thing
是string
,从string
(带有Exclude<keyof T, K>
)中排除任何内容,由于您发现令人惊讶的签名,仍将导致string
。