【问题标题】:How to support mixin-like object merging如何支持类 mixin 的对象合并
【发布时间】:2021-09-13 20:02:55
【问题描述】:

我正在将现有代码库转换为 TypeScript。

虽然我认识到 TypeScript's Mixin infrastructure 的价值,但我需要支持一个代码库,该代码库只是将属性从公共模块复制到类实例。

这是一个非常简化的示例来说明我试图支持的结构:

// Common base class
class BaseClass {
    baseFeature():boolean {
        return true;
    }
}

// Common mixin module
const mixin = {
    mixinFeatureOne():boolean {
        return this.baseFeature();
    },
    mixinFeatureTwo():boolean {
        return this.mixinFeatureOne();
    }
} as ThisType<BaseClass>;

// Specific module that extends the base class and combines the mixin
class DerivedClass extends BaseClass {
    constructor() {
        super();
        Object.assign(this, {
            ...mixin,
        });
    }
}

const instance = new DerivedClass();

TypeScript 卡在 mixin 对象上。

没有大量进一步的修改,我似乎仅限于两种选择:

  1. 要么保留 mixin 对象的隐式类型(即默认推断类型),在这种情况下我无法访问基本类型的函数

  2. 或者我可以将 mixin 对象定义为 ThisType&lt;BaseClass&gt;,这使我可以访问基类函数,但我不能再访问 mixin 中的函数。

我确定我在这里做错了,但到目前为止我发现的唯一解决方案是创建一个复制 mixin 对象的类型,然后将 mixin 声明为 ThisType 和 mixin 类型的组合。这非常繁琐,因为每次添加或修改函数时都需要复制定义。

type MixinType = BaseClass & {
    mixinFeatureOne():boolean;
    mixinFeatureTwo():boolean;
};

const mixin = {
    mixinFeatureOne():boolean {
        return this.baseFeature();
    },
    mixinFeatureTwo():boolean {
        return this.mixinFeatureOne();
    }
} as ThisType<MixinType>;

也许这只是这种不符合标准的mixin策略必须付出的惩罚,但我觉得这应该更容易一些。有没有办法通过扩展推断的类型而不是重新声明它来简化这一点?像这样的:

const mixin = {
    mixinFeatureOne():boolean {
        return this.baseFeature();
    },
    mixinFeatureTwo():boolean {
        return this.mixinFeatureOne();
    }
} as ThisType<BaseClass & __inferred_type__>;

(我也知道我会遇到 DerivedType 没有 mixin 功能的问题。我很高兴将其留给未来的问题(如果我想不通的话)我自己出),但我很感激,除了主要问题,你在这方面有任何意见或建议。

【问题讨论】:

    标签: typescript mixins


    【解决方案1】:

    你可以在每个 mixin 方法中显式输入this

    type Mixin = {
      mixinFeatureOne: () => boolean
      baseFeature: () => boolean
    }
    
    // Common base class
    class BaseClass {
      baseFeature(): boolean {
        return true;
      }
    }
    
    // Common mixin module
    const mixin = {
      mixinFeatureOne(this: Mixin): boolean {
        return this.baseFeature();
      },
      mixinFeatureTwo(this: Mixin): boolean {
        return this.mixinFeatureOne();
      }
    };
    
    // Specific module that extends the base class and combines the mixin
    class DerivedClass extends BaseClass {
      constructor() {
        super();
        Object.assign(this, {
          ...mixin,
        });
      }
    }
    
    const instance = new DerivedClass();
    instance.
    

    Playground

    但是,instance 仍然不知道 mixin 方法。

    如果您希望 Mixin 模式的类型更加安全,请考虑以下示例:

    // credits goes to https://stackoverflow.com/a/50375286
      type UnionToIntersection<U> =
        (U extends any ? (k: U) => void : never) extends (
          k: infer I
        ) => void
        ? I
        : never;
    
      type ClassType = new (...args: any[]) => any;
      
      function Mixin<T extends ClassType, R extends T[]>(...classRefs: [...R]):
        new (...args: any[]) => UnionToIntersection<InstanceType<[...R][number]>> {
        return merge(class { }, ...classRefs);
      }
    
      function merge(derived: ClassType, ...classRefs: ClassType[]) {
        classRefs.forEach(classRef => {
          Object.getOwnPropertyNames(classRef.prototype).forEach(name => {
            // you can get rid of type casting in this way
            const descriptor = Object.getOwnPropertyDescriptor(classRef.prototype, name)
            if (name !== 'constructor' && descriptor) {
              Object.defineProperty(
                derived.prototype,
                name,
                descriptor
              );
            }
          });
        });
    
        return derived;
      }
    
      class Foo {
        foo() { }
      }
    
      class Bar {
        bar() { }
      }
    
      class Baz {
        baz() {
          console.log('baz');
        }
      }
    
      class MyClass extends Mixin(Foo, Bar, Baz) { }
    
      const my = new MyClass();
      my.foo() // ok
      my.bar() // ok
      my.baz(); // ok
    
    

    Playground

    完整的解释可以在我的article找到

    如果你对这种方法没问题,我会在答案中提供更多解释

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-08
      • 1970-01-01
      • 1970-01-01
      • 2018-10-05
      • 1970-01-01
      • 2022-11-18
      • 2022-11-19
      • 1970-01-01
      相关资源
      最近更新 更多