使用typescript扩展/继承泛型时出现问题



我在typescript中将泛型与接口扩展结合起来时遇到了问题。我的基本用例是:

  1. 基本接口
  2. 从父接口扩展的子接口(仅1级深度继承(
  3. 每个子接口都包含不在基本接口中的数据
  4. 某些字段可能在不同的同级接口之间共享,也可能不共享

我希望能够编写一个类型安全的泛型函数,它可以正确地识别子接口,包括将参数适当地匹配到泛型接口。

我已经成功地为扩展接口使用了鉴别器,但在将其与其他参数联系起来时遇到了困难。例如:

这是我们的基本类型,以及鉴别器值"0";类型":

interface Base<T> {
type: T
a: string
b: number
}

以下是可能的扩展/继承者:

type ExtensionType = 'CDate' | 'DBool' | 'CString'
interface ExtensionCDate<T = 'CDate'> extends Base<T> {
c: Date
}
interface ExtensionDBool<T = 'DBool'> extends Base<T> {
d: boolean
}
interface ExtensionCString<T = 'CString'> extends Base<T> {
c: string
}

这里的想法是ExtensionCDate只能接受"CDate"的泛型值,因此它的类型值总是"CDate",等等。

以下是我试图解决这个问题的方法:

  1. 使用可能的子接口的并集类型:
type GenericExtension<T extends ExtensionType> = ExtensionCDate | ExtensionDBool | ExtensionCString
  1. 为每个子接口生成一个相应的接口,该接口仅包括子接口中不存在于基本接口中的字段(本质上设置差异(:
type ExtendedData<T extends ExtensionType> = Omit<GenericExtension<T>, keyof Base<T>>
  1. 编写一个函数,使用歧视来正确处理每个可能的孩子:
const genericFunc = <T extends ExtensionType>(obj: GenericExtension<T>, data: ExtendedData<T>): void => {
switch ( obj.type ) {
case 'CDate':
obj.c = data.c  // data.c should exist
return
case 'DBool':
obj.d = data.d  // data.d should exist
return 
case 'CString':
obj.c = data.c  // data.c should exist
return
}
}

不幸的是,虽然obj的类型似乎有效,但鉴别器并不适用于数据参数。我知道我可以使用强制转换(data.c作为ExcludedData<'Date'>(,但我发现这有一个很糟糕的解决方案,并不理想。

我得到的错误如下:

TS2339: Property 'c' does not exist on type 'Pick  , never>'.

这让我觉得我至少对ExtendedData类型做得不正确。我觉得解决方案应该相当简单。我在这里错过了什么?

谢谢!请告诉我是否可以提供任何其他上下文来帮助制定解决方案。

依赖于变量子类型T的泛型和特定子类型的实例之间存在一些混淆。

type GenericExtension<T extends ExtensionType> = ExtensionCDate | ExtensionDBool | ExtensionCString

在上一行中,您已经声明了一个泛型T,但实际上还没有在类型定义中使用T

interface ExtensionCDate<T = 'CDate'> extends Base<T> {
c: Date
}

在特定的扩展中,您实际使用<T = 'CDate'>所做的是,如果未设置T,则将字符串文字CDate设置为T默认值,但实际上它仍然可以设置为任何值,因为我们没有对其施加任何限制。

您希望特定的扩展类型不再使用通用T变量。相反,我们手动为Base<T>设置T

interface ExtensionCDate extends Base<'CDate'> {
c: Date
}
interface ExtensionDBool extends Base<'DBool'> {
d: boolean
}
interface ExtensionCString extends Base<'CString'> {
c: string
}

由于我们的CCD_;通用的";在typescript的意义上,我们将其称为AnyExtension。此类型是三种特定扩展类型的并集。

type AnyExtension = ExtensionCDate | ExtensionDBool | ExtensionCString

实际上,我们可以从中导出三种类型字符串的并集,而不是写出它。

type ExtensionType = AnyExtension['type'] // evaluates to type "CDate" | "DBool" | "CString"

我们仍然会遇到switch语句的一些问题,因为基于obj.type的切换细化了typescript对obj类型的知识,而不是对T的知识。尽管您知道如果obj.type === "CDate"T也必须是"CDate",但typescript并没有实现这一飞跃,因此不会在switch的基础上改进其对data类型的知识。

不幸的是,我认为您确实需要某种as断言,至少在data变量上是这样。

最新更新