如何为可区分联合的超集创建通用约束

How do I make a generic constraint for a superset of a discriminated union

在定义泛型class Foo<X>时,其中X旨在成为可区分的联合类型,有没有办法表达"X must be a superset of the discriminated union Y"?

我有一种情况,我正在使用可区分的联合来表示不同的动作类型。这个真实世界的上下文是一个使用 Redux 的应用程序,所以每个动作都是不同的类型,具有不同的负载,而 Redux reducer 是一个可以接受任何动作的函数,所以我使用动作类型的可区分联合来描述动作参数。

在下面的示例中,它类似于我的真实问题,我有一个可扩展的基础 class,它知道如何处理 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);

Playground Link

我的代码生成错误:

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 的 "extension",但是从 class 继承的角度考虑这一点让我意识到 extends 可能不是这样做的正确方法。 (即,如果 class A 扩展 class B,则 A 拥有 B 中的所有字段,再加上更多。而 ExtendedActionTypesBaseActionTypes 之间的关系不正确。

是否有更好的方法来表达对可区分联合的约束,例如对两个可区分联合类型 X 和 Y 说 "X must by a superset of Y"?

通用类型在这里如何与 extend 一起工作是我们限制我们的 class 最终将包含来自 BaseActionTypes 的一个或多个可能的成员。对于 union types extends 意味着 - 所有可分配给该类型的东西,这意味着它可以具有相同数量或更少的变体,但仅此而已。你问你的类型 ExtendedActionTypes 如果它有一个选项 more .. 真的不是,那只是因为你没有填写类型的实现,这三个都和 TS 一样是结构类型语言。如果你添加到这些类型和属性你将有一个错误,check this here.

所以 ExtendedActionTypes 不能分配给 BaseActionTypes 因为它有更多的选择。

你的错误虽然有不同的原因,因为你试图将类型 Walk 的值设置为 ActionTypes extends BaseActionTypes 的值。这意味着 ActionTypes 是不包含 Walk 的可能类型,因此您无法执行此类赋值。证明下方:

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

如您所见,Walk 无法分配给 Run

可能的问题修复之一是从 class 中完全删除约束:

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);

Playground link

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

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);

Playground link

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