TypeScript 类混入不应该要求构造函数具有特定的签名,

cgvd09ve  于 9个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(116)

TypeScript版本: 2.2.0-dev.20170209
代码

要求mixin具有constructor(...args: any[])构造函数签名会导致一些问题:

  1. Node v4是一个活跃的长期支持版本,但不支持rest和spread语法。我们避免使用它,即使针对ES6,也可以在Node v4上运行我们的输出。mixin构造函数要求防止这种情况。
  2. 通常会有希望提供特定构造函数的mixin。这些通常会最后应用以创建具体类。现在这是不可能的,所以你必须创建一个只有构造函数的新具体类,以给它正确的签名。
  3. 对于具有构造函数参数要求的mixin,从...args中提取参数非常繁琐。
    以下是我遇到的一个例子。我有两个共享基类的类:
  1. class Base {
  2. foo: string[];
  3. }
  4. class A extends Base {}
  5. class B extends Base {}

后来,我想通过mixins添加一些功能来创建两个新类:

  1. type Constructor<T extends object> = new (...args: any[]) => T;
  2. interface Magic {}
  3. const Magic = <T extends Constructor<Base>>(superclass: T): Constructor<Magic>& T =>
  4. class extends superclass implements Magic {
  5. constructor(foo: string[]) {
  6. super();
  7. this.foo = foo;
  8. }
  9. };
  10. const MagicA = Magic(A);
  11. const MagicB = Magic(A);

我想让MagicA和MagicA具有接受单个参数的构造函数。我知道代码将起作用,因为我知道A和B的构造函数,但类型检查器不满意。
首先,在这种情况下Constructor类型是错误的,因为我知道自己想要的签名。实际上,我希望写成这样:

  1. type BaseConstructor = new() => Base;
  2. type MagicConstructor = new(foo: string[]) => Magic;
  3. interface Magic {}
  4. const Magic = <T extends BaseConstructor>(superclass: T): MagicConstructor & T =>
  5. class extends superclass implements Magic {
  6. constructor(foo: string[]) {
  7. super();
  8. this.foo = foo;
  9. }
  10. };
  11. const MagicA = Magic(A);
  12. const MagicB = Magic(A);

预期行为:

这可以工作

实际行为:

错误:“类型'T'不是构造函数类型。”出现在扩展子句上。

kqqjbcuj

kqqjbcuj1#

在我看来,有很多使用场景。如果你需要一个灵活且功能丰富的mixin,你通常需要指定使用你的mixin的类的(构造函数)依赖关系。你希望将你的mixin限制在一组类中,例如控制器类,并希望访问像当前用户这样的属性,而不强制超状态包含这个属性。特别是如果mixin是从库中导入的,你真的不希望强制库的用户为了使用mixin而改变他的父类结构。

mcdcgff0

mcdcgff02#

+1 - 我的用例也是关于构造函数依赖注入的。我的混入限制了可能的基类,而那个基类有构造函数依赖。我希望强制混入的子类向构造函数提供正确的依赖项。就像常规(子)类一样。如果受限制的基类的构造函数签名发生变化,那么应该出现编译错误。

我目前使用以下解决方法:

  1. class MyDependency {
  2. }
  3. class MyBaseType {
  4. // Changing the constructor signature here will not cause an error
  5. // inside the MyMixin constructor
  6. constructor(readonly dep: MyDependency) {
  7. }
  8. }
  9. type Constructor<T extends object> = new (...args: any[]) => T;
  10. type MyBaseTypeConstructor = new (dep: MyDependency) => MyBaseType;
  11. function MyMixin<T extends MyBaseTypeConstructor>(base: T) {
  12. // Ideally this cast would not be required
  13. // For now it solves the "Type 'T' is not a constructor function type"
  14. const baseConstructor: Constructor<MyBaseType> = base;
  15. return class extends baseConstructor {
  16. constructor(dep: MyDependency) {
  17. super(dep); // This call is not type-checked, because of ...any[] rest argument
  18. }
  19. };
  20. }
  21. class ExampleUsage extends MyMixin(MyBaseType) {
  22. constructor(dep: MyDependency) {
  23. super(dep); // This should be type-checked to be the same signature as MyBaseType
  24. }
  25. }

使用这个解决方法,仅更改 MyBaseType 构造函数签名就可以得到编译错误(这就是我想要的)。然而,在更改 MyBaseTypeConstructor 为正确签名后,不要忘记更改 MyMixin 构造函数,因为在只更改 MyBaseTypeConstructor 之后,你将不会再收到任何错误。那将是一个问题,因为 MyMixin 的子类应该在这一点上导致编译错误。幸运的是,在更新 MyMixin 内的构造函数后,你会得到剩余的错误,这些错误需要修复。

展开查看全部
dpiehjr4

dpiehjr43#

另一个模式(在JS中)是传递一个包含任何构造函数都可以读取的属性的options对象,而不考虑顺序。super()调用栈中的任何构造函数都可以提取所需的内容。

这很好,大多数情况下它只是工作得很好,只要层次结构中的所有类和mixin都使用单个options参数约定,并且不要在属性名称上发生冲突(这与示例属性/方法名称上的冲突问题相同,因此我们应该努力实现唯一命名)。

csbfibhn

csbfibhn4#

使用类型Assert,一切皆有可能😊。这对于mixin的用户来说效果很好(尽管编写mixin有点像黑魔法):

  1. type ComposeConstrucrtor<T, U> =
  2. [T, U] extends [new (a: infer O1) => infer R1,new (a: infer O2) => infer R2] ? {
  3. new (o: O1 & O2): R1 & R2
  4. } & Pick<T, keyof T> & Pick<U, keyof U> : never
  5. function Foo<T extends new (o: any) => any>(Base: T) {
  6. class Foo extends (Base as new (...a: any[]) => {}) {
  7. constructor(options: { foo: string }) {
  8. super(options)
  9. console.log(options.foo)
  10. }
  11. foo() {}
  12. static foo() {}
  13. }
  14. return Foo as ComposeConstrucrtor<typeof Foo, T>
  15. }
  16. function Bar<T extends new (o: any) => any>(Base: T) {
  17. class Bar extends (Base as new (...a: any[]) => {}) {
  18. constructor(options: { bar: string }) {
  19. super(options)
  20. console.log(options.bar)
  21. }
  22. bar() {}
  23. static bar() {}
  24. }
  25. return Bar as ComposeConstrucrtor<typeof Bar, T>
  26. }
  27. class Baz extends Foo(Bar(class { })) {
  28. constructor(options: { foo: string, bar: string, whatever: string }) {
  29. super(options)
  30. console.log(options.whatever)
  31. }
  32. baz() {}
  33. static baz() {}
  34. }
  35. let baz = new Baz({ bar: "", foo: "", whatever: ""});
  36. baz.bar();
  37. baz.baz();
  38. baz.foo()
  39. Baz.foo();
  40. Baz.bar();
  41. Baz.baz();
展开查看全部
vngu2lb8

vngu2lb85#

@justinfagnani

关于相关问题,你在其他mixin中使用mixin时遇到过问题吗?似乎不起作用,在应用___ is not a constructor function mixin的extends部分时,显示#32004

相关问题