【问题标题】:Why does this TypeScript mixin employing generics fail to compile?为什么这个使用泛型的 TypeScript mixin 无法编译?
【发布时间】:2019-12-12 07:49:09
【问题描述】:

我正在将 mixins/traits 与 TypeScript 一起使用,使用子类工厂模式,如 https://mariusschulz.com/blog/mixin-classes-in-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;
            ~~~~

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

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

【问题讨论】:

  • @T.J.Crowder 问题现在绝对是最小的。感谢您提供的任何帮助。
  • 我没有使用通用特征(看起来很有趣!)。但是很好地减少了这个!现在看起来真的很负责。我 ping 了一个我认识的人。
  • @T.J.Crowder ty -- 希望我能有所收获。这对我来说是一个真正的障碍。 ://
  • @MatthewAdams 感谢减少代码,我也想看它,但因代码太多而无法查看

标签: typescript generics traits mixins typescript-generics


【解决方案1】:

这个问题其实很简单,和打字稿没有部分类型参数推断有关。调用Identifiable&lt;string&gt;(...) 并不意味着您设置ID 并让编译器推断T。这实际上意味着将string 用于ID 并使用默认值(即Empty)用于T。不幸的是,有一个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

【讨论】:

  • 谢谢,@titian-cernicova-dragomir。我又尝试了另一种变体,typescriptlang.org/play/#code/…,但它坏了。有什么想法吗?
  • 仅供参考,使用 Identifiable 时的更改:class Person2 extends Identifiable&lt;string, Nothing&gt;(Nameable()) { ... }。在这里,我试图通过显式列出类型参数来完全不使用推理。
  • @MatthewAdams 但是如果name 没有从Nameable() 流向Identifiablename 应该来自哪个类?如果您想使用显式类型,这将是一个有效的版本:
  • 啊,好的。我说很糟糕。在 TypeScript 解决问题之前,我将坚持使用类型化的虚拟参数。再次感谢! :)
猜你喜欢
  • 1970-01-01
  • 2014-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多