Typescript:类如何使用不同的类型参数扩展与其基类相同的接口?

时间:2019-02-26 01:22:53

标签: typescript

我正在尝试允许两个类扩展相同的接口,但具有不同的类型参数。我有SomeTypedInterface,扩展SomeBaseClass的SomeClass和扩展ISomeBaseProps的ISomeProps。我希望SomeClass和SomeBaseClass都使用各自的道具来扩展SomeTypedInterface。

注意:这两个类都不实际实现SomeTypedInterface或ISome * Props。幕后发生了一些不可思议的事情,与这个问题没有太大关系。长话短说,SomeTypedInterface中的函数和ISome * Props中的属性将自动添加到这些类的实例中。我只是想让编译器知道这里正在使用的类型,以便我们在使用这些对象时具有良好的智能感知。

这是有问题的简单示例:

interface SomeTypedInterface<TProps>
{
    getProps(): TProps;
}

interface ISomeBaseClassProps
{
    baseStringProp: string;
}

interface SomeBaseClass extends ISomeBaseClassProps,
    SomeTypedInterface<ISomeBaseClassProps> { }
class SomeBaseClass
{
    ...
}

interface ISomeClassProps extends ISomeBaseClassProps
{
    booleanProp: boolean;
}

interface SomeClass extends ISomeClassProps,
    SomeTypedInterface<ISomeClassProps> { }
class SomeClass extends SomeBaseClass
{
    ...
}

这会出现以下错误:

Interface 'SomeClass' cannot simultaneously extend types 'SomeBaseClass' and 'SomeTypedInterface<ISomeClassProps>'.
Named property 'getProps' of types 'SomeBaseClass' and 'SomeTypedInterface<ISomeClassProps>' are not identical.

其他一些信息:

  1. 对于消费类而言,我们需要尽可能简单地实现它-SomeTypedInterface实际上非常复杂,因此正确的声明合并是不可能的
  2. 基类可能实际上不会在TypeScript中定义,而是纯粹具有在这些接口中指定的一组自动添加的函数和属性。
  3. 定义类时传递给SomeTypedInterface的I * Props接口将始终扩展基类的I * Props接口。

我真正要寻找的是一种告诉编译器的方法,即SomeClassdInterface的SomeClass扩展覆盖相同接口的SomeBaseClass扩展,对SomeTypedInterface进行(简单或复杂)更改和/或对SimpleTypedInterface进行简单更改。类的接口。这将是一种广泛使用的模式,因此在单个实现中的简单性至关重要。

3 个答案:

答案 0 :(得分:1)

也许可以代替声明合并(或除了声明合并)而在类中声明属性?喜欢:

class SomeClass extends SomeBaseClass {
  getProps!: () => ISomeClassProps;  // note the !
}

可以用您之前做过的任何魔术来完成实际的实现,但是现在SomeClass不再抱怨getProps的类型了。请注意definite assignment assertion (!),以使--strictPropertyInitialization不会打扰您。

有帮助吗?

答案 1 :(得分:1)

这是另一种方法,假设您需要以编程方式创建类属性,并且无法手动添加它们。让我们创建一个帮助函数,该函数返回SomeBaseClass构造函数并将其断言为较窄的类型。然后,可以让您的子类扩展该函数的返回值,并且它应具有与声明合并类似的作用:

// helper function asserting that SomeBaseClass returns instances of a narrower type T
const assertSomeBaseClass = <T extends SomeBaseClass>() => SomeBaseClass as (new () => T);

// don't do declaration merging, instead extend an asserted-to-be-narrower class:
class SomeClass extends assertSomeBaseClass<
  ISomeClassProps & SomeTypedInterface<ISomeClassProps>
>() {
  //...
}

// test the properties
declare const someClass: SomeClass;
someClass.baseStringProp; // string
someClass.booleanProp; // boolean
someClass.getProps(); // ISomeClassProps

至少在我对您的用例了解的情况下,这似乎可行。希望能有所帮助。再次祝你好运!

答案 2 :(得分:0)

我想我找到了我可以接受的解决方案。

我不是让基类和派生类都扩展了接口,而是让基类接受了泛型,然后使用其自己的props和泛型的组合类型来扩展接口。然后,派生类将扩展基类。

结果是两个类都使用不同的类型参数扩展了接口。

interface SomeTypedInterface<TProps>
{
    getKeys(): keyof TProps;
}

interface ISomeBaseClassProps
{
    baseStringProp: string;
}

interface SomeBaseClass<TProps extends ISomeBaseClassProps = ISomeBaseClassProps> extends ISomeBaseClassProps,
    SomeTypedInterface<TProps & ISomeBaseClassProps> { }
class SomeBaseClass<TProps>
{
    ...
}

interface ISomeClassProps extends ISomeBaseClassProps
{
    booleanProp: boolean;
}

interface SomeClass extends ISomeClassProps { }
class SomeClass extends SomeBaseClass<ISomeClassProps>
{
    ...
}

此解决方案有两个缺点,但是我认为这对于我的用例都是可以接受的。

  1. 在泛型类型方面,Intellisense并不完美。在基类中时,它将getKeys()的返回值显示为keyof TProps | "baseStringProp。它在派生类中完美运行,因为该类不需要泛型。
  2. 类声明有点混乱。

在我们的场景中,我们正在使用的“类”中的很大一部分实际上并不需要类声明(问题的“其他信息”部分中的#2),所以我认为这是对于较不常见的多级继承情况,可以接受的解决方案(虽然肯定不是理想的)。