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

Mat*_*ams 1 generics traits mixins typescript typescript-generics

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

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";
  }
}
Run Code Online (Sandbox Code Playgroud)

编译错误是

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

30     this.name = name;
            ~~~~
Run Code Online (Sandbox Code Playgroud)

如何让通用特征正确编译,而不管它们的使用顺序如何?

注意:此问题的公共 git 存储库位于https://github.com/matthewadams/typetrait。如果你想玩这个,一定要检查minimal分支。

Tit*_*mir 5

这个问题其实很简单,和typescript没有部分类型参数推断有关。调用Identifiable<string>(...)并不意味着您设置ID并让编译器推断T. 它实际上意味着 use stringforID和使用默认值(即Empty) for T。这是不幸的,并且有一个允许部分推理提议,但它没有获得太多的关注。

您有两种选择,要么使用函数柯里化来执行两次调用方法,其中第一个调用通过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";
  }
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

或者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";
  }
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接