我有一些ES6 mixin代码,如下所示:
function DeserializableMixin(Base) {
return class extends Base {
deserialize(structure) {
Object.assign(this, JSON.parse(JSON.stringify(structure)));
return this;
}
clone() {
const c = this.constructor;
return new c().deserialize(this);
}
};
}
这适用于现有代码:
class FooEntity {}
class DeFoo extends DeserializableMixin(FooEntity) {}
const df = new DeFoo();
const x = df.deserialize({ bar: 43 });
const y = df.clone();
我想将这个mixin移植到TS并基于https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-mix-in-classes的文档,它似乎相当简单。
我希望反序列化为某种类型安全并采用Partial<Base>
而不是Partial<any>
或Partial<{}>
,以便IDE /编译器可以拾取与序列形状不匹配的反序列化基类。
同样我不想使用Partial<this>
,因为这会导致派生类上的函数(例如clone()和deserialize())被允许作为结构的有效部分。
我想写的是:
export type Constructor<T> = new(...args : any[]) => T;
export function DeserializableMixin<T extends Constructor<{}>>(Base : T) {
return class extends Base {
deserialize(structure : Partial<Base> ) {
Object.assign(this, JSON.parse(JSON.stringify(structure)));
return this;
}
clone() {
const c = this.constructor as Constructor<this>;
return new c().deserialize(this as Base);
}
};
}
但是typescript不允许我使用Base作为类类型,即使它允许我在extends子句中使用它。
我接近它的唯一方法是执行以下操作:
export function DeserializableMixin<P, T extends Constructor<{}>>(Base : T, proto : P) {
return class extends Base {
deserialize(structure : Partial<P> ) {
Object.assign(this, JSON.parse(JSON.stringify(structure)));
return this;
}
clone() {
const c = this.constructor as Constructor<this>;
return new c().deserialize(this as any as P);
}
};
}
这要求我像这样使用mixin:
class Foo {
bar : number;
baz : string;
};
class DeFoo extends DeserializableMixin(Foo, Foo.prototype) {}
const df = new DeFoo();
const x = df.deserialize({bar: 43})
const y = df.clone();
做了这项工作,但对我来说似乎有点愚蠢。是否有一种不那么复杂的方式呢?
修改
我后来发现即使这还不够。如果混合的使用保留在与定义混合的模块相同的模块中,那么这很好,但如果我为此导出类型定义并在另一个模块中使用它,例如:
const df = new DeFoo();
const df2 = df.clone();
无法按预期工作,因为df2
将具有any
类型。请注意,如果它是在同一个模块中完成的(定义了混合模块),df2的类型为DeFoo。
为了解决这个问题,我添加了更多看似冗余的代码。
export function DeserializableMixin<P, T extends Constructor<{}>>(Base : T, proto : P) {
return class extends Base {
deserialize<E>(this : E, structure : Partial<P> ) : E {
Object.assign(this, JSON.parse(JSON.stringify(structure)));
return this;
}
clone<E>(this : E) : E {
const c = this.constructor as any;
return new c().deserialize(this as any as P);
}
};
}