问题描述
我正在尝试在TypeScript中实现 mixins 。
这是代码:
type Mixable = new (...args: any[]) => {};
class Base {
constructor(protected _a: string,protected _b: string) {}
}
const A = (Base: Mixable) => class A extends Base {
a() {
console.log(this._a);
return this;
}
};
const B = (Base: Mixable) => class B extends Base {
b() {
console.log(this._b);
return this;
}
};
const C = A(B(Base));
const o = new C("a","b");
o.a().b();
但是不幸的是,编译器无法识别混合对象,链方法和发出的错误之间共享的属性。
P.S。如果运行此命令,它将成功运行,并生成预期的输出,而不会出现任何JS错误。
解决方法
为了访问属性_a
和_b
,您需要强制将要应用mixin的类具有这些属性。
mixins在具有protected
属性的打字稿中也可能很棘手,所以我将它们设为public
。
interface HasAB {
_a: string;
_b: string;
}
type Mixable = new (...args: any[]) => HasAB
class Base {
constructor(public _a: string,public _b: string) {}
}
这可以清除访问属性的问题。
编辑:
不幸的是,我一直在玩这个游戏,只是不能与非公共财产一起很好地工作。使用this setup,组成的类能够访问传入类的受保护属性,但是它们给我们带来错误TS4094“导出的类表达式的属性'_a'可能不是私有的或受保护的。”因为mixin的属性必须是公开的。
我没有足够的信心说这不可能直截了当,但是如果我是你,我会考虑采用另一种设计模式。您可以将访问器方法getA()
和getB()
添加到类Base
中。或者,您可以定义readonly public
属性a
和b
,以访问基础private
属性_a
和_b
。
这是第二种情况的分解方式:
我们定义一个interface AB
,它具有public
个属性a
和b
。回想一下,打字稿界面中的所有属性本来就是public
。
interface AB {
a: string;
b: string;
}
我们声明如果可以用关键字Mixable
创建某事物并且该创建的对象具有new
和a
,则认为该事物b
。
type Mixable = new (...args: any[]) => AB;
我们更改Base
使其实现interface AB
,这将使其变为Mixable
。您可以显式编写class Base implements AB
,但不必这样做。只要它可以读取属性Mixable
和a
,它就可以分配给b
。我们使用javascript getter将a
和b
实现为readonly
属性,这些属性将读取_a
和_b
的私有值,但不能对其进行修改。
class Base {
constructor( protected _a: string,protected _b: string) {}
get a(): string {
return this._a;
}
get b(): string {
return this._b;
}
}
现在是我们的mixins。我们说要混合的类必须是Mixable
类型。我们使用通用的<T extends Mixable>(Mixed: T)
,以便我们知道返回的类将具有传入类的所有属性和方法,而不仅仅是Mixable
中的那些。这样可以解决链接问题。
请注意,我在示例中重命名了方法,因为我使用了a
和b
作为属性的名称,所以我也不能将它们用作方法的名称。
const A = <T extends Mixable>(Mixed: T) => class A extends Mixed {
aMethod() {
console.log(this.a);
return this;
}
};
const B = <T extends Mixable>(Mixed: T) => class B extends Mixed {
bMethod() {
console.log(this.b);
return this;
}
};
现在您不再对此有任何疑问:
const C = A(B(Base)); // fine because Base extends Mixable
const o = new C("a","b");
o.aMethod().bMethod(); // fine because o.aMethod() returns an object with all of the abilities of C.
,
谢谢大家的贡献。它为这个问题增加了巨大的价值。
在进一步介绍之前,让我先解释一下我的初衷以及代码中的误解。
使用mixins的主要目的是分解大型类。由于这将应用于不同的类,因此我正在创建一个帮助器模块。为了使示例易于理解,我发布了一个更简单的实现。
关于Base
标识符,我并不是说Base
参数是指基类,而是父类(即扩展名的基类)。无论如何,为了摆脱混乱,我将改用标识符Parent
。
因此,我想出了一些我认为可以扩展的内容。请忽略怪异的名称Libbable
(这只是为了说明这一点)。
type Mixable<T = {}> = new (...args: any[]) => T;
interface Libbable {
x: string;
a(): any;
}
type Mixin = Mixable<Libbable>;
class Lib {
constructor(public x: string) {}
a(): this { return this }
}
const A = <T extends Mixin>(Parent: T) => class extends Parent {
a() {
console.log(this.x);
return this;
}
};
const B = <T extends Mixin>(Parent: T) => class extends Parent {
b() {
return this.a();
}
};
const L = A(B(Lib));
const o = new L("x");
o.a().b();
由于the design issue当前,TypeScript无法在混合对象之间共享非公共属性。我真的很希望能够使用此功能,否则,强大的访问修饰符在这里根本没用。
由于必须在Base类中声明方法才能在mixins中使用它们,因此一旦调用mixins的适用类方法,它们就会被调用。这不是我想要发生的事情。 JS允许我们摆脱这种情况,但TS不允许。如果您有更好的主意实现我想要的目标,请随时分享。