为什么这个使用泛型的 TypeScript mixin 编译失败?

Why does this TypeScript mixin employing generics fail to compile?

我将 mixins/traits 与 TypeScript 一起使用,使用子 class 工厂模式,如 https://mariusschulz.com/blog/mixin-classes-in-typescript 所述。所讨论的特征称为 Identifiable,它将 id 属性 传递给 class,该 class 应该表达 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;
            ~~~~

无论使用顺序如何,如何使通用特征正确编译?

注意:public git 这个问题的回购在 https://github.com/matthewadams/typetrait。如果你想玩这个,一定要检查 minimal 分支。

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

您有两个选择,要么使用函数柯里化进行两次调用方法,其中第一个调用传递 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