为什么使用泛型的这种TypeScript mixin无法编译?

时间:2019-08-04 14:49:15

标签: typescript generics traits mixins typescript-generics

我正在使用https://mariusschulz.com/blog/mixin-classes-in-typescript中所述的通过子类工厂模式将Mixins / traits与TypeScript一起使用。有问题的特征称为Identifiable,它为应该表达id特征的类赋予Identifiable属性。当我尝试按特定顺序将特征与另一个非通用特征(Nameable)一起使用时,编译会失败。

class Empty {}

type ctor<T = Empty> = new(...args: any[]) => T;

function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
  return class extends superclass {
    public name?: string;
  };
}

function Identifiable<ID, T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
  return class extends superclass {
    public id?: ID;
  };
}

class Person1 extends Nameable(Identifiable<string>()) { // compiles
  constructor(name?: string) {
    super();
    this.name = name;
    this.id = "none";
  }
}

class Person2 extends Identifiable<string>(Nameable()) { // fails to compile
  constructor(name?: string) {
    super();
    this.name = name;
    this.id = "none";
  }
}

编译错误为

src/test/unit/single.ts:30:10 - error TS2339: Property 'name' does not exist on type 'Person2'.

30     this.name = name;
            ~~~~

无论如何使用通用特征,我如何都能使其正确编译?

NB:此问题的公共git回购位于https://github.com/matthewadams/typetrait。如果您想玩这个游戏,请确保签出minimal分支。

1 个答案:

答案 0 :(得分:4)

这个问题实际上很简单,这与打字稿没有部分类型实参推论的事实有关。调用Identifiable<string>(...)并不意味着您设置了ID并让编译器推断T。实际上,这意味着对string使用ID,对Empty使用默认值(即T)。这很不幸,虽然有proposal to allow partial inference,但并没有获得太大的吸引力。

您有两个选择,可以使用函数currying进行两次调用,其中第一次调用通过ID,第二次调用推断T

class Empty { }

type ctor<T = Empty> = new (...args: any[]) => T;

function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
  return class extends superclass {
    public name?: string;
  };
}

function Identifiable<ID>() {
  return function <T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
    return class extends superclass {
      public id?: ID;
    };
  }
}


class Person2 extends Identifiable<string>()(Nameable()) {
  constructor(name?: string) {
    super();
    this.name = name;
    this.id = "none";
  }
}

Playground link

或通过将虚拟参数用作推断位点来对ID进行推断:

class Empty { }

type ctor<T = Empty> = new (...args: any[]) => T;

function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
  return class extends superclass {
    public name?: string;
  };
}

function Identifiable<ID, T extends ctor = ctor<Empty>>(type: ID, superclass: T = Empty as T) {
    return class extends superclass {
      public id?: ID;
    };
  }
}


class Person2 extends Identifiable(null! as string, Nameable()) {
  constructor(name?: string) {
    super();
    this.name = name;
    this.id = "none";
  }
}

Playground link