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;
定义了 a 也需要所有属性,为什么在省略其中一个属性时它会变成更宽松的类型?我希望返回类型仍然是Pick<Thing, "name" | "description">
.
有谁知道发生了什么?
解决方案
打字稿 4.1+ 的更新
由于 typeScript 4.1在映射类型中引入了键重映射,您现在可以编写一个不难看的版本,Omit
它可以更优雅地处理具有索引签名的类型:
type NewOmit<T, K extends PropertyKey> =
{ [P in keyof T as Exclude<P, K>]: T[P] };
您可以验证它是否按需要工作:
type RemoveId = NewOmit<Thing, "id">;
/* type RemoveId = {
[x: string]: any;
name: string;
description: string;
} */
好的!
预打字稿 4.1 答案
你可以制作一个Omit<>
处理索引签名的,但它真的很难看。
如前所述,keyof T
如果T
有一个字符串索引签名是string | number
,并且任何文字键(如'id'
)都会被吃掉。可以通过一些疯狂的类型系统箍跳来获得具有索引签名的类型的文字键。一旦你有了文字键和索引签名键,你就可以建立一个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
内容,请将其放入某个无需任何人查看的库中,然后在您的代码中使用它;这取决于你。我不知道它处理所有可能的边缘情况,甚至在某些情况下“正确”的行为是什么(例如,你可以同时拥有 astring
和 a值比值窄的number
索引......你怎么办?想看看你是否属于这种类型?♂️)所以请自行承担风险。但我只想指出,某种解决方案是可能的。number
string
Omit<T, string>
好的,希望有帮助。祝你好运!
推荐阅读
- sql - SQL 字符串拆分和更新字段
- windows - 在停止的 Oracle 容器数据库中获取可插拔数据库?
- javascript - 如何获得我在 range 函数中声明的数组的总和?
- github - 语义发布中的变更日志处理?
- powerpoint - Office PowerPoint 加载项:演示开始后更新内容
- ruby-on-rails - Nokogiri 安装失败 libxslt 丢失
- reactjs - 错误:无效的挂钩调用。在等待之后调用钩子时 - 你如何等待来自一个钩子的数据实例化到另一个钩子?
- c++ - c ++:g ++在cmd上看不到库
- reactjs - 将 props 传递给 styled-components 选择器
- grails - 如何在 zip 文件中生成 xls 文件并将其发送到电子邮件以在那里下载 - groovy