inheritance - 使用打字稿扩展/继承泛型时遇到问题
问题描述
我在打字稿中将泛型与接口扩展相结合时遇到了麻烦。我的基本用例是这样的:
- 基础接口
- 从父接口扩展的子接口(只有 1 级深度继承)
- 每个子接口都包含不在基本接口中的数据
- 某些字段可能会或可能不会在各种兄弟接口之间共享
我希望能够编写一个类型安全的泛型函数,该函数可以正确识别子接口,包括适当地匹配泛型接口的参数。
我已经成功地为扩展接口使用了鉴别器,但是在将它与其他参数联系起来时遇到了麻烦。例如:
这是我们的基本类型,以及一个鉴别器值“Type”:
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' 等。
这是我试图解决这个问题的方法:
- 使用可能的子接口的联合类型:
type GenericExtension<T extends ExtensionType> = ExtensionCDate | ExtensionDBool | ExtensionCString
- 为每个子接口生成一个对应的接口,该接口仅包含子接口中存在的字段,而基接口中不存在(本质上是设置差异):
type ExtendedData<T extends ExtensionType> = Omit<GenericExtension<T>, keyof Base<T>>
- 编写一个函数,使用歧视来正确处理每个可能的孩子:
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 参数。我知道我可以使用强制转换 (data.c as ExcludedData<'CDate'>),但我发现它有一个 hacky 解决方案,并不理想。
我得到的错误如下:
TS2339: Property 'c' does not exist on type 'Pick , never>'.
这让我觉得我至少错误地使用了 ExtendedData 类型。我觉得解决方案应该相当简单。我在这里想念什么?
谢谢!让我知道我是否可以提供任何额外的上下文来帮助提出解决方案。
解决方案
泛型(取决于变量 subtype T
)和特定子类型的实例之间存在一些混淆。
type GenericExtension<T extends ExtensionType> = ExtensionCDate | ExtensionDBool | ExtensionCString
在上面的行中,您已经声明了一个泛型T
,但实际上并没有T
在您的类型定义中使用。
interface ExtensionCDate<T = 'CDate'> extends Base<T> {
c: Date
}
在特定的扩展中,您实际上所做的<T = 'CDate'>
是将字符串文字设置为if未设置CDate
的默认值,但实际上它仍然可以设置为任何值,因为我们没有对其施加任何限制。T
T
您想要的是您的特定扩展类型不再采用通用T
变量。相反,我们手动T
设置Base<T>
.
interface ExtensionCDate extends Base<'CDate'> {
c: Date
}
interface ExtensionDBool extends Base<'DBool'> {
d: boolean
}
interface ExtensionCString extends Base<'CString'> {
c: string
}
由于我们GenericExtension
实际上并不是打字稿意义上的“通用”,所以让我们AnyExtension
改为调用它。这种类型是三种特定扩展类型的联合。
type AnyExtension = ExtensionCDate | ExtensionDBool | ExtensionCString
我们实际上可以从中导出三种类型字符串的并集,而不是写出来。
type ExtensionType = AnyExtension['type'] // evaluates to type "CDate" | "DBool" | "CString"
我们仍然会遇到该switch
语句的一些问题,因为基于切换obj.type
改进了打字稿对 T 类型obj
而不是 T类型的了解。即使您知道如果obj.type === "CDate"
必须T
如此, typescript"CDate"
也不会实现这一飞跃,因此不会data
基于switch
.
不幸的是,我认为您确实需要某种as
断言,至少在data
变量上是这样。
推荐阅读
- c# - 我如何从随机图片框中计算点数
- deployment - 无法部署虚拟助手模板:'术语 az 未被识别为 cmdlet 的名称
- java - 更喜欢私有静态方法而不是实例方法
- java - Bridj 不再适用于 Windows 任务栏加载
- excel - 根据单元格值更改行颜色 - 如果值相同,则应用相同的单元格值
- android - 暂停 Android ndk 应用程序时如何保留 EGL 上下文
- ruby-on-rails - Ruby short IF 更短?
- flutter - 如果出现以下错误,如何在 Flutter 中运行小部件测试?
- markdown - 如何使用 Pandoc 将降价页面放到新的 PDF 页面上?
- python - 如何在 Google Cloud Functions 中处理 Python 函数中的线程?