让 TypeScript 推断泛型参数

问题描述 投票:0回答:1

我有一个基础类型和一个后代类型

interface IInferredTypeBase {
}
interface IInferredType extends IInferredTypeBase {
    myProperty;
}

实际上,该等级制度中有许多后代。

现在,我还有一个对于该层次结构通用的类型:

interface IGenericForInferredType<T extends IInferredTypeBase> {
}

我想要的是让 TypeScript 在这样的情况下自动插入泛型参数:

type ICallback<TInferredType extends IInferredTypeBase, TGenericForInferredType extends IGenericForInferredType<TInferredType>> = (instance: TInferredType) => any;

function register<
    TInferredType extends IInferredTypeBase,
    TGenericForInferredType extends IGenericForInferredType<TInferredType>
>(
    param1: new () => TGenericForInferredType,
    callback: ICallback<TInferredType, TGenericForInferredType>
) {
    //...
}

因此,有

register
函数接受 2 个参数:
param1
IGenericForInferredType<TInferredType>
类型的任何类型的描述符,只是为了让另一个参数
callback
将其参数推断为
TInferredType
。但它不起作用。

那么,让我们举一个具体的例子。
这门课是为

param1
:

class ClassImplementingGenericForInferredType implements IGenericForInferredType<IInferredType> {
}

这是一个调用示例:

register(ClassImplementingGenericForInferredType, (instance) => {})

但是

instance
的类型是基本类型
IInferredTypeBase
而不是
IInferredType

我怎样才能让它推断

IInferredType

这意味着当我调用

IInferredType
时,我不想在任何地方写下
register
这个词,我希望 TypeScript 帮我解决这个问题。

Motivation:

ClassImplementingGenericForInferredType
,
IInferredType
,
IInferredTypeBase
--> 这些是从C#生成的,它们的层次和通用关系是在C#中定义的,我不想写2x

这里有一个 TypeScript Playground 链接演示了该问题,作为后备,还有一个 github 存储库

typescript
1个回答
0
投票

TypeScript 的类型系统是结构,而不是名义。 所以像这样的通用

类型
interface Gen<T extends Base> {}

泛型类型参数

T
在定义中没有出现(它只是
{}
)对
T
没有结构依赖性。 因此不可能从中推断出
T
。 在上面,
Gen<A>
Gen<B>
都只是
{}
,并且查看
{}
无法可靠地为您提供有关
A
B
的任何信息。 请参阅相关 TS 常见问题解答条目

你需要一些结构依赖,比如

interface Gen<T extends Base> {
    t: T;
}

再次

type ICallback<T extends Base, G extends Gen<T>> = (instance: T) => any;

G
没有结构依赖性,因此你不可能从中推断出
G
。 对于这种类型,
G
大概没有任何作用。我们可以从该定义中删除
G
,事实上,完全删除
ICallback
因为间接寻址不会给我们带来任何东西;我们可以只使用
(t: T) => any


从这里开始,您需要确保拥有尽可能少的泛型类型参数。使您的示例工作所需的最少代码如下所示

function register<T extends Base>(
  param1: new () => Gen<T>, 
  callback: (t: T) => any
) { }    

interface Ext extends Base {
    myProperty: string;
}
class ClassImplementingGenericForInferredType implements Gen<Ext> {
    declare t: Ext
}
register(ClassImplementingGenericForInferredType, (instance) => {
    instance.myProperty = "...";
})

这里用

register()
调用
ClassImplementingGenericForInferredType
允许 TypeScript 推断
T
Ext
。 请注意,类声明中的
implements
子句
不会导致推理成功。此类子句对推理没有任何影响,仅用于事后对类主体进行类型检查。 (您可以删除 implements 子句,看看它不会改变什么。)正是
t
类型的
Ext
属性让 TypeScript 推断出
T
。 (如果你尝试删除
t
属性,你会看到一切都会崩溃。)同样,结构类型是 TypeScript 的基础;声明网站并没有那么重要。
一旦

T

被推断为

Ext
,那么回调参数
instance
上下文类型就是
Ext
,因此
instance.myProperty
存在。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.