typescript - Typescript 可交换的 Mixin 组合
问题描述
假设我有一个带有方法的基本编码器类
encode(obj: {body: Uint8Array}): Uint8Array
obj.body
我将它扩展为使用一个/多个 mixins接受不同的类型 - 即:
function numberMixin(encoder) {
return class extends encoder {
encode(obj) {
if (isNumberObj(obj)) {
...
return super.encode(transformed);
}
return super.encode(obj);
}
}
}
并组成这些mixin
const Encoder = etcMixin(numberMixin(stringMixin(Base)));
有没有办法注释每个 mixin / 编码器,以便生成的类的 encode 方法知道它接受哪些类型?我可以指定输入/输出功能,但是 mixin 顺序是固定的,不能轻易扩展。
一种选择是在每个 mixin 上使用泛型类型参数,但是我必须在组合的每个步骤中指定类型,这似乎是多余的/过于冗长。
numberMixin<U, T extends Constructor<IEncoder<U>>>(encoder: T): T & Constructor<IEncoder<Number|U>>;
解决方案
我们可以利用函数类型的交集被视为与重载函数相同的事实,因此我们可以将 encode 的类型构造为 being BaseClass['encode'] & newOverlaodSignature
。
唯一的问题是我们不能使用方法语法来做到这一点,我们需要使用函数字段语法,这意味着我们将声明字段,但手动将其分配给原型。此外,由于我们手动添加函数,我们无法使用super.
手动调用基类实现所需的语法,因此我们在那里失去了一些类型安全性。
好消息是调用站点看起来不错,并且所有重载都存在:
编辑
来自评论的额外功劳,也收集类型以便我们可以在其他 mixin 中使用它们。如果我们为类型添加一个额外的静态属性,我们可以做到这一点,该属性将包含每个添加类型的字段。我们需要一个字段而不是类型字段的原因是返回的类型将是AddedStuff & T
这样,这将向下渗透到类型字段,我们type
从所有 mixin 中获得所有已定义字段的交集。使用printEncodedMixin
额外的类型信息:
class EncoderBase {
constructor(param: string) {
}
encode(obj: { body: Uint8Array }): Uint8Array {
return obj.body;
}
static type: { Uint8Array: Uint8Array }
}
type EncoderType = {
new (...args: any[]) : { encode: (obj: { body: Uint8Array }) => Uint8Array }
type: any
}
function numberMixin<T extends EncoderType>(encoder: T) {
function isNumberObj(obj: any): obj is { body: number } {
return obj && typeof obj.body === 'number';
}
let resultClass = class extends encoder {
encode!: InstanceType<T>['encode'] & ((obj: { body: number }) => Uint8Array);
static type : { number : number }
}
resultClass.prototype.encode = function (obj: any) {
if (isNumberObj(obj)) {
return encoder.prototype.encode(obj);
}
return encoder.prototype.encode(obj);
}
return resultClass;
}
function stringMixin<T extends EncoderType>(encoder: T) {
function isStringObj(obj: any): obj is { body: string } {
return obj && typeof obj.body === 'string';
}
let resultClass = class extends encoder {
encode!: InstanceType<T>['encode'] & ((obj: { body: string }) => Uint8Array);
static type : { string : string }
}
resultClass.prototype.encode = function (obj: any) {
if (isStringObj(obj)) {
return encoder.prototype.encode(obj);
}
return encoder.prototype.encode(obj);
}
return resultClass;
}
function printEncodedMixin<T extends EncoderType>(encoder: T) {
type Body<T> = T extends any ? {body : T} : never;
return class extends encoder {
printEncoded(b: Body<T['type'][keyof T['type']]>) {
}
}
}
const Encoder = printEncodedMixin(numberMixin(stringMixin(EncoderBase)));
let d = new Encoder(""); // ctor params still work
d.encode({ body: "" });
d.encode({ body: 0 });
d.encode({ body: {} }); // Error
d.printEncoded({ body: "" }) // printEncoded({body: string;} | {body: number;} | {body: Uint8Array;}): void
推荐阅读
- odata - Fiori Elements - 自定义 $batch 查询
- google-apps-script - 如何将 onChange() 谷歌应用脚本函数绑定到特定事件?
- react-native - createMaterialBottomTabNavigator 在 ios 中不起作用
- css - Woocommerce 添加到购物车按钮跳转
- python - 我的正则表达式不取第二个数字
- python - 从python中的字符串中删除带幂的数字
- ruby-on-rails - 使用非标准字符搜索和获取结果
- python - 使用 Flask 无法正确显示图像
- javascript - 我的 for 循环只输出最后一个结果,我可以改变这个吗?
- python - 如何aiohttp请求发布文件列表python请求模块?