TypeScript 中类型安全的 mixin 装饰器
Type-safe mixin decorator in TypeScript
我尝试定义类型安全的 mixin()
装饰器函数,如下所示,
type Constructor<T> = new(...args: any[]) => T;
function mixin<T>(MixIn: Constructor<T>) {
return function decorator<U>(Base: Constructor<U>) : Constructor<T & U> {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Base.prototype[name] = MixIn.prototype[name];
});
return Base as Constructor<T & U>;
}
}
并使用如下,
class MixInClass {
mixinMethod() {console.log('mixin method is called')}
}
/**
* apply mixin(MixInClass) implicitly (use decorator syntax)
*/
@mixin(MixInClass)
class Base1 {
baseMethod1() { }
}
const m1 = new Base1();
m1.baseMethod1();
m1.mixinMethod(); // error TS2339: Property 'mixinMethod' does not exist on type 'Base1'.
然后,编译器说 m1
没有成员 'mixinMethod'
。
生成的代码如下,
//...
var Base1 = /** @class */ (function () {
function Base1() {
}
Base1.prototype.baseMethod1 = function () { };
Base1 = __decorate([
mixin(MixInClass)
], Base1);
return Base1;
}());
//...
看来 mixin
装饰器应用正确。
所以,在我的理解中,m1
的类型被推断为Base1 & MixIn
。但是编译器说它只是 Base1
.
我使用 tsc 2.6.2
并使用 --experimentalDecorators
标志编译这些代码。
为什么编译器无法像我预期的那样识别类型?
根据@jcalz的回答,我修改了我的代码如下,
type Constructor<T> = new(...args: any[]) => T
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin(MixIns) {
class Class{ };
for (const MixIn of MixIns) {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Class.prototype[name] = MixIn.prototype[name];
});
}
return Class;
}
class MixInClass1 {
mixinMethod1() {}
}
class MixInClass2 {
mixinMethod2() {}
}
class Base extends mixin([MixInClass1, MixInClass2]) {
baseMethod() { }
}
const x = new Base();
x.baseMethod(); // OK
x.mixinMethod1(); // OK
x.mixinMethod2(); // OK
x.mixinMethod3(); // Property 'mixinMethod3' does not exist on type 'Base' (Expected behavior, Type check works correctly)
这很好用。我想针对可变长度混合 类.
改进此 mixin
函数
一种解决方案是添加重载函数声明,如下所示,
function mixin<T1>(MixIns: [Constructor<T1>]): Constructor<T1>;
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin<T1, T2, T3>(MixIns: [Constructor<T1>, Constructor<T2>, Constructor<T3>]): Constructor<T1&T2&T3>;
但这太丑了。有什么好主意吗?在支持 variadic-kind 之前是不可能的吗?
装饰器不会按照您期望的方式改变装饰后 class 的类型签名。有一个 rather lengthy issue in Github 对此进行了讨论,目前尚不清楚是否就应如何(或是否)实施此类变更达成一致。现在的主要问题是编译器将 Base1
理解为未修饰的 class,并且没有修饰版本的名称。
从阅读那个 Github 问题来看,建议的解决方法(至少现在)看起来是这样的:
class Base1 extends mixin(MixInClass)(
class {
baseMethod1() { }
}) {
}
所以你没有使用装饰器 @
符号,而是直接将装饰器函数应用于匿名 class(它与你想要的 Base1
具有相同的实现) ,然后 subclassing that 得到 Base1
。现在编译器知道 Base1
有一个 baseMethod1()
和一个 mixinMethod()
。
希望对您有所帮助。祝你好运!
我尝试定义类型安全的 mixin()
装饰器函数,如下所示,
type Constructor<T> = new(...args: any[]) => T;
function mixin<T>(MixIn: Constructor<T>) {
return function decorator<U>(Base: Constructor<U>) : Constructor<T & U> {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Base.prototype[name] = MixIn.prototype[name];
});
return Base as Constructor<T & U>;
}
}
并使用如下,
class MixInClass {
mixinMethod() {console.log('mixin method is called')}
}
/**
* apply mixin(MixInClass) implicitly (use decorator syntax)
*/
@mixin(MixInClass)
class Base1 {
baseMethod1() { }
}
const m1 = new Base1();
m1.baseMethod1();
m1.mixinMethod(); // error TS2339: Property 'mixinMethod' does not exist on type 'Base1'.
然后,编译器说 m1
没有成员 'mixinMethod'
。
生成的代码如下,
//...
var Base1 = /** @class */ (function () {
function Base1() {
}
Base1.prototype.baseMethod1 = function () { };
Base1 = __decorate([
mixin(MixInClass)
], Base1);
return Base1;
}());
//...
看来 mixin
装饰器应用正确。
所以,在我的理解中,m1
的类型被推断为Base1 & MixIn
。但是编译器说它只是 Base1
.
我使用 tsc 2.6.2
并使用 --experimentalDecorators
标志编译这些代码。
为什么编译器无法像我预期的那样识别类型?
根据@jcalz的回答,我修改了我的代码如下,
type Constructor<T> = new(...args: any[]) => T
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin(MixIns) {
class Class{ };
for (const MixIn of MixIns) {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Class.prototype[name] = MixIn.prototype[name];
});
}
return Class;
}
class MixInClass1 {
mixinMethod1() {}
}
class MixInClass2 {
mixinMethod2() {}
}
class Base extends mixin([MixInClass1, MixInClass2]) {
baseMethod() { }
}
const x = new Base();
x.baseMethod(); // OK
x.mixinMethod1(); // OK
x.mixinMethod2(); // OK
x.mixinMethod3(); // Property 'mixinMethod3' does not exist on type 'Base' (Expected behavior, Type check works correctly)
这很好用。我想针对可变长度混合 类.
改进此mixin
函数
一种解决方案是添加重载函数声明,如下所示,
function mixin<T1>(MixIns: [Constructor<T1>]): Constructor<T1>;
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin<T1, T2, T3>(MixIns: [Constructor<T1>, Constructor<T2>, Constructor<T3>]): Constructor<T1&T2&T3>;
但这太丑了。有什么好主意吗?在支持 variadic-kind 之前是不可能的吗?
装饰器不会按照您期望的方式改变装饰后 class 的类型签名。有一个 rather lengthy issue in Github 对此进行了讨论,目前尚不清楚是否就应如何(或是否)实施此类变更达成一致。现在的主要问题是编译器将 Base1
理解为未修饰的 class,并且没有修饰版本的名称。
从阅读那个 Github 问题来看,建议的解决方法(至少现在)看起来是这样的:
class Base1 extends mixin(MixInClass)(
class {
baseMethod1() { }
}) {
}
所以你没有使用装饰器 @
符号,而是直接将装饰器函数应用于匿名 class(它与你想要的 Base1
具有相同的实现) ,然后 subclassing that 得到 Base1
。现在编译器知道 Base1
有一个 baseMethod1()
和一个 mixinMethod()
。
希望对您有所帮助。祝你好运!