【发布时间】:2021-01-15 07:41:56
【问题描述】:
我在 typescript 中将泛型与接口扩展相结合时遇到了麻烦。我的基本用例是这样的:
- 基本界面
- 从父接口扩展的子接口(仅 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),但我发现这有一个 hacky 解决方案,并不理想。
我得到的错误如下:
TS2339: Property 'c' does not exist on type 'Pick , never>'.
这让我觉得我至少错误地使用了 ExtendedData 类型。 我觉得解决方案应该相当简单。我在这里错过了什么?
谢谢!让我知道是否可以提供任何额外的上下文来帮助提出解决方案。
【问题讨论】:
标签: inheritance typescript-generics type-safety