我需要确保以下泛型抽象类的类型安全的帮助,该类使用代理:
代理.ts
function authHandler<
F extends Families,
T extends controllerAbstract<F>
>(): any {
return {
get: function (target: T, prop: keyof T): any {
return (...args: any[]) => {
target.authorize((controllerClient: Client<F>) => {
return target[prop](controllerClient, args);
});
};
},
};
}
控制器摘要.ts
abstract class controllerAbstract<F extends Families> {
controllerFamily: F;
constructor(controllerFamily: F) {
this.controllerFamily = controllerFamily;
return new Proxy(this, authHandler<F, typeof this>());
}
abstract build_client(): Client<F>;
authorize(consume_client: (client: Client<F>) => typeof this[keyof typeof this]) {
consume_client(this.build_client());
}
}
混凝土.ts
const controllerFamilyName = 'FamilyA';
type ControllerFamliy = typeof controllerFamilyName;
class FamilyAController extends controllerAbstract<ControllerFamliy> {
constructor() {
super(controllerFamilyName);
}
build_client(): Client<ControllerFamliy> {
// build a client of type "FamilyA"
const client = { id: 'A' } as Client<ControllerFamliy>;
return client;
}
// proxy "authorize" provides client object
useClient(client: any, args: any[]) {
// i want client to implicitly be of type "FamilyAClient_t" / Client<"FamilyA">
// currently just using "any" as placeholder
}
}
类型.ts**
type FamilyAClient_t = { id: 'A' };
type FamilyBClient_t = { id: 'B' };
interface ClientTable {
FamilyA: FamilyAClient_t;
FamilyB: FamilyBClient_t;
}
type Families = keyof ClientTable;
type Client<F extends Families> = ClientTable[F];
注意:这里我说的“concrete”类,我指的是concrete.ts的内容,也就是说,它是一个扩展抽象类的类,FamilyAController
只是一个例子。
我的目标是使concrete.ts的 FamilyAController 中useClient
方法的client
参数反映基于具体类的controllerFamilyName
常量的正确类型,这样我就可以调用client.id,并且如果controllerFamilyName == 'FamilyA'
,则得到正确的自动完成“A”,如果controllerFamilyName == 'FamilyB'
,则得到“B”。
基于here解决方案,我已经更改了代理中的显式类型,以尝试获得所需的识别类型。这就是我认为类型安全在我的实现中出现故障的地方。
然而,这仍然是错误的,因为:
- 代理
target[prop](...)
行给出了一个错误“this expression is not callable”,因为target[prop]
的类型是unknown。2这是因为并非target的每个属性都是可调用的,但有些属性是可调用的,我希望能够将target的类型限制在这个子集。 - 我尝试为抽象类
authorize
方法的consume_client
函数参数定义返回类型:authorize(consume_client: (client: Client<F>) => typeof this[keyof typeof this])
.会掷回错误。目的是让它传回在concrete中定义之任何方法传回型别的型别。例如,如果另一个方法存在于concrete类别上,那么消费客户端返回类型应该能够自动处理这个新的方法返回类型。代理层维护基于具体类的 * 任何 * 可调用方法而存在的相同返回类型。
另一个尝试过的方法是定义另一个抽象类,其中只包含与target[props](controllerClient)
签名一致的抽象方法,并使用keyof
这个类作为道具类型,以修复第一个错误。这是成功的,但我仍然不确定如何达到我的最终目标。
本质上我想要的是,在 concrete.ts 中,能够写useClient(client)
,并且客户端类型隐式地是Client<ControllerFamily>
,这样我就不必为每个具体类变体的每个方法写useClient(client: Client<ControllerFamily>)
。这可能吗?
希望这有助于澄清我编写代码的目标,以及我目前的进度。
1条答案
按热度按时间icnyk63a1#
不幸的是,TypeScript目前无法赋予继承自超类的类方法参数“隐式”或“上下文”类型。您需要手动注解所有方法参数,然后可以针对超类 * 检查 * 这些参数。所以如果你做错了,你会得到一个错误。但是你仍然必须手动操作。这绝对是令人讨厌的。已经有很多功能要求改进它(例如,请参见microsoft/TypeScript#23911),但这些都没有进入语言。似乎总是有一个性能问题,或者破坏现实世界的代码。目前,你能做的最好的事情将是手动注解。
话虽如此,我能得到的最接近你想要的是尝试告诉编译器
ControllerAbstract
实际做什么......也就是说,任何添加的方法都必须接受两个参数,第一个参数是Client<F>
类型,对应于相关的generic类型参数F
。我说“尝试”是因为没有完美的方法来完成这个任务,没有直接的支持来描述“除了已知键之外的所有
string
”,所以没有办法说“ControllerAbstract<F>
上除了"controllerFamily"
、"buildClient"
和"authorize"
“。在microsoft/TypeScript#17867上有一个长期的开放特性请求,但谁知道它何时或是否会被实现。目前只有解决方案(请参见How to define Typescript type as a dictionary of strings but with one numeric "id" property以获得这些解决方案的列表)。一种解决方法是将对象的已知部分与“其他所有内容”部分的索引签名相交。
但这是不真实的;这意味着,比如说,
buildClient
属性将 * 既是 *() => Client<F>
类型的方法 * 又是 *(client: Client<F>, args: any[]) => any
类型的方法。编译器知道这不是真的,所以你不能直接把索引签名添加到类体中。相反,你需要做我上面做的事情:重命名实际的类体,并使用类型Assert来使编译器相信ControllerAbstract
是一个行为符合要求的构造函数:好吧,让我们看看它是否有效:
看起来不错。
useClient()
方法类型检查,而useClientBad()
方法不检查,并且那里的错误描述了问题,client
是FamilyBClient_t
,而它应该是FamilyAClient_t
。所以,你去那里。它不是完美的,但它是我能得到的最接近。
Playground代码链接