如何在TypeScript中检测类是否应用Mixin并推导其类型?
TypeScript Mixin类型判断与智能提示实现方案
问题背景
需要判断TypeScript类是否应用了特定Mixin,以此缩小类型范围获得自动补全/智能提示,但尝试两种方案均失败:
方案1:Mixin内部命名类
在Mixin内部定义命名类,但该类仅在Mixin调用时创建,外部无法访问,导致instanceof无法识别:
class A { } let BMixin = (superclass: any) => class B extends superclass { bProp: string = "bProp"; bMethod() { console.log("method from B"); } }; const C = BMixin(A) const c = new C; console.log(c instanceof B) // 错误:找不到名称'B'
方案2:工具函数复制属性
创建接收类返回Mixin的工具函数,但需要手动复制所有方法/属性,且instanceof始终返回false:
class A { } class B { bProp: string = "bProp"; bMethod() { console.log("method from B"); } } function defineMixin (mixinClass: any) { return (arg0: any) => class extends arg0 { // 需手动复制方法和属性 } } const D = defineMixin(B)(A) const d = new D; console.log(d instanceof B) // false
期望效果
- 类型守卫后获得Mixin的智能提示:
if (hasMixin(something, BMixin)) { // something自动提示BMixin的所有属性和方法 }
- 链式Mixin后自动合并所有类型:
const Something = CMixin(BMixin(A)) const something = new Something; // something自动提示A、B、C的所有属性和方法
现有库局限
ts-mixer库提供hasMixin函数,但会修改基类原型,且存在this/super使用限制。
解决方案
通过给Mixin添加唯一标识+类型守卫函数+类型自动合并实现,不修改原原型链,兼容链式调用:
1. 定义Mixin基础类型与工具函数
// 带唯一标识的Mixin类型 type Mixin<T extends new (...args: any[]) => any> = T & { readonly __mixinId: symbol; }; // 创建带唯一标识的Mixin function createMixin<T extends new (...args: any[]) => any>( mixinClass: T ): Mixin<T> { const mixin = mixinClass as Mixin<T>; mixin.__mixinId = Symbol(mixin.name); return mixin; } // 应用Mixin并合并类型 function applyMixin< Base extends new (...args: any[]) => any, Mixin extends new (...args: any[]) => any >(base: Base, mixin: Mixin) { class Mixed extends base { constructor(...args: any[]) { super(...args); // 复制Mixin实例属性 Object.assign(this, new mixin()); } } // 复制Mixin原型方法与静态属性 Object.assign(Mixed.prototype, mixin.prototype); Object.assign(Mixed, mixin); // 类型层面合并基础类与Mixin的实例类型 return Mixed as typeof Mixed & (new (...args: ConstructorParameters<Base>) => InstanceType<Base> & InstanceType<Mixin>); }
2. 实现类型守卫函数
// 判断实例是否应用了指定Mixin的类型守卫 function hasMixin<T extends Mixin<any>>(instance: unknown, mixin: T): instance is InstanceType<T> { let proto = Object.getPrototypeOf(instance as object); while (proto) { if (proto.constructor?.__mixinId === mixin.__mixinId) { return true; } proto = Object.getPrototypeOf(proto); } // 兜底检查实例是否包含Mixin的核心属性 return Object.prototype.hasOwnProperty.call(instance, Object.keys(mixin.prototype)[0]); }
3. 使用示例
// 基础类 class A { aProp = "aProp"; aMethod() { console.log("A method"); } } // 创建带标识的Mixin class B { bProp = "bProp"; bMethod() { console.log("B method"); } } const BMixin = createMixin(B); class C { cProp = "cProp"; cMethod() { console.log("C method"); } } const CMixin = createMixin(C); // 链式应用Mixin const Something = applyMixin(applyMixin(A, BMixin), CMixin); const something = new Something(); // 自动提示所有属性和方法 something.aMethod(); something.bMethod(); something.cMethod(); // 类型守卫示例 function test(obj: unknown) { if (hasMixin(obj, BMixin)) { console.log(obj.bProp); // 自动提示bProp obj.bMethod(); // 自动提示bMethod } if (hasMixin(obj, CMixin)) { console.log(obj.cProp); obj.cMethod(); } } test(something); // 触发两个守卫分支
方案优势
- 不修改原类原型链,仅创建新子类,避免原型污染
- 运行时通过唯一
symbol标识判断,准确可靠 - TypeScript自动推断合并后的类型,完美支持智能提示
- 兼容
this与super的正常使用 - 支持任意链式Mixin组合
内容的提问来源于stack exchange,提问作者GermanJablo
相关产品推荐
相关产品推荐

