如何为有区别并集的超集生成泛型约束



当定义一个泛型类Foo<X>时,其中X是一个可判别的并集类型,有没有一种方法可以表示"X必须是可判别并集Y的超集"?

我有一种情况,我用一个受歧视的工会来代表不同的行动类型。现实世界中的上下文是一个使用Redux的应用程序,因此每个操作都是具有不同有效载荷的不同类型,而Redux reducer是一个可以接受任何操作的函数,因此我使用操作类型的区分并集来描述操作参数。

在下面的例子中,类似于我的实际问题,我有一个可扩展的基类,它知道如何处理BaseActionTypes,并且我希望能够将ExtendedTypes作为泛型参数传递


interface Run {
}
interface Walk {
}

type BaseActionTypes = Run | Walk
interface Jump {
}
type ExtendedActionTypes = BaseActionTypes | Jump;
class ActionDoer<ActionTypes extends BaseActionTypes> {
doAction(a: ActionTypes) {
}
walk() {
const w: Walk = {};
this.doAction(w); // ERROR!
}
}
class ExtendedActionDoer extends ActionDoer<ExtendedActionTypes> {
}
const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {};
extendedActionDoer.doAction(j);

游乐场链接

我的代码生成错误:

Argument of type 'Walk' is not assignable to parameter of type 'ActionTypes'.
'Walk' is assignable to the constraint of type 'ActionTypes', but 'ActionTypes' could be instantiated with a different subtype of constraint 'BaseActionTypes'.(2345)

我不太清楚为什么基础ActionDoer不能在这里doAction(w)。我试图添加一个约束,即"无论传递的动作的并集是ActionTypes,它都必须至少包括并集BaseActionTypes中的一组动作,并且可能包括其他动作。或者,换句话说,ActionTypes必须是BaseActionTypes的超集。">

我想ActionTypes extends BaseActionTypes可能不是我想做的事情的正确约束?最初extends似乎是正确的,因为ExtendedActionTypesBaseActionTypes的"扩展",但从类继承的角度思考这一点让我意识到extends可能不是正确的方法。(即,如果类A扩展了类B,则A具有B中的所有字段,再加上更多字段。而ExtendedActionTypesBaseActionTypes之间的关系不成立。

有没有更好的方法来表达对可判别并集的约束,即对两个可判别并类型X和Y说"X必须由Y的超集"?

泛型类型在extend中的工作方式是,我们约束我们的类最终将包含BaseActionTypes中的一个或多个可能成员。对于并集类型,extends意味着所有可分配给该类型的内容,这意味着它可以有相同数量或更少的变体,但不能更多。你问如果你的类型ExtendedActionTypes多了一个选项,它是如何分配的。。事实并非如此,这只是因为你没有填充类型的实现,这三者都是一样的,因为TS是结构类型语言。如果添加到这些类型和属性中,则会出现错误,请在此处进行检查。

因此CCD_ 20不可分配给CCD_ 21,因为它具有更多而不是更少的选项。

然而,您的错误有不同的原因,因为您试图将类型为Walk的值设置为ActionTypes extends BaseActionTypes的值。这意味着ActionTypes是不包括Walk的可能类型,因此您无法执行此类分配。项目下方:

// no error as `Run` extends `BaseActionTypes`
class ExtendedActionDoer extends ActionDoer<Run> {
}

如您所见,Walk不能分配给Run

一个可能的问题解决方案是从类中删除约束:

type ExtendedActionTypes = BaseActionTypes | Jump;
class ActionDoer {
doAction<ActionType extends BaseActionTypes>(a: ActionType) {
}
walk() {
const w: Walk = {type: 'Walk'};
this.doAction(w);
}
}
class ExtendedActionDoer extends ActionDoer {
doAction(a: ExtendedActionTypes) {
}
}
const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {type: 'Jump'};
extendedActionDoer.doAction(j);

游乐场链接

我们也可以保留通用,但需要有属性,考虑:

type ExtendedActionTypes = Jump;
class ActionDoer<ActionType> {
doAction(a: ActionType | BaseActionTypes) {
}
walk() {
const w: Walk = {type: 'Walk'};
this.doAction(w);
}
}
class ExtendedActionDoer extends ActionDoer<ExtendedActionTypes> {
doAction(a: ExtendedActionTypes) {
}
}
const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {type: 'Jump'};
extendedActionDoer.doAction(j);

游乐场链接

最重要的是——doAction(a: ActionType | BaseActionTypes)我说,无论你给我什么,我都会接受,但总会有BaseActionTypes的成员。

最新更新