【问题标题】:How to assert two interfaces contain the same keys in TypeScript?如何断言两个接口在 TypeScript 中包含相同的键?
【发布时间】:2021-07-05 16:28:41
【问题描述】:

给定接口FooBar,我如何静态地断言这两种类型具有相同的键?

背景:我有一个接口,它既需要用作数据库 DTO,又需要用作“模型”类型。对于数据库 DTO,原语将是常规的 JavaScript 原语。对于“模型”类型,原语将是标称类型(例如 AccountId 而不是 string 等)。目前,我通过定义 2 个接口来实现这一点:一个用于 DTO,一个用于模型。但是,我想要一些方法来确保这些接口不会漂移:

interface Dto {
  orderId: string;
  invoiceNo: number;
}
interface Model {
  orderId: OrderId;
  invoiceNo: InvoiceNumber;
}
assertKeysEqual<Dto, Model>() // Compiles OK
interface Dto {
  orderId: string;
  invoiceNo: number;
  x: any;
}
interface Model {
  orderId: OrderId;
  invoiceNo: InvoiceNumber;
}
assertKeysEqual<Dto, Model>() // Compile ERROR ('Model' does not contain 'x')
interface Dto {
  orderId: string;
  invoiceNo: number;
}
interface Model {
  orderId: OrderId;
  invoiceNo: InvoiceNumber;
  x: any;
}
assertKeysEqual<Dto, Model>() // Compile ERROR ('Dto' does not contain 'x')

名义类型使用以下方法定义。 但是,更一般地说:键类型的形状可能在类型之间完全不同。例如,在一种类型中,您可能有 order: OrderId,它可以在其“扩展”对应类型中替换为 order: OrderEntity

export interface OrderId extends String {
  _OrderId: string;
}
export function OrderId(value: string): OrderId {
  return value as any;
}

知道如何实现assertKeysEqual 之类的东西吗?

【问题讨论】:

  • 如何定义 OrderId 和 InvoiceNumber 类型?因为如果我理解你的问题,只需定义 type OrderId = string;类型 InvoiceNumber = 号​​码;并创建一个可用于 Dto 和 Model 对象的单一接口
  • OrderIdInvoiceNumber 是标称类型:我已经更新了描述。

标签: typescript typescript-generics


【解决方案1】:

还有一个仅类型的解决方案,它会给出一些可读的错误消息:

type AssertKeysEqual<
  T1 extends Record<keyof T2, any>,
  T2 extends Record<keyof T1, any>
> = never

type Assertion = AssertKeysEqual<{a:1}, {a:1, b: 'x'}>
// ERROR: Property 'b' is missing in type '{ a: 1; }' but required in type 'Record<"a" | "b", any>'.

TypeScript playground

更新:在 TypeScript 4.2+ 中,类型别名保留 (docs) 允许稍微更愉快的错误消息,如果您引入额外的类型 ShapeOf 进行记录:

type ShapeOf<T> = Record<keyof T, any>
type AssertKeysEqual<X extends ShapeOf<Y>, Y extends ShapeOf<X>> = never

type Assertion = AssertKeysEqual<{a:1}, {a:1, b: 'x'}>
// ERROR: Property 'b' is missing in type '{ a: 1; }' but required in 
// type 'ShapeOf<{ a: 1; b: "x"; }>'.

TypeScript playground

【讨论】:

    【解决方案2】:

    您可以检查每个接口的键是否是另一个的子集:

    type AssertEqualKeys<T1 extends object, T2 extends object> =
      [
        keyof T1 extends keyof T2 ? 1 : 0,
        keyof T2 extends keyof T1 ? 1 : 0
      ] extends [1,1] ?  true : false;
    
    const areEqual: AssertEqualKeys<Dto,Model> = true;
    

    如果密钥相互偏离,该类型将解析为 false,并且您会收到错误通知。

    playground link

    【讨论】:

    • 天哪,类型系统在 ts 中变得非常先进
    • 考虑到最初问题中的编辑,不确定此解决方案是否有效。一些“类型”可能是一个函数,这不清楚......
    • @Jerome OP here:键的类型确实可以是函数,但 T1T2 将始终是对象。我认为这个解决方案效果很好。
    • 如果我能接受 2 个答案,我会的。谢谢你的解决方案,@chrisbajorin
    猜你喜欢
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    • 2012-08-23
    • 2022-12-12
    • 2014-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多