简体   繁体   中英

Is there a way to compose class mixins in TypeScript?

I have a hierarchy of class mixins that work in plain JavaScript.

const
  AsFoo = ( superclass ) => class extends superclass {
    get foo(){ return true; }
  },

  AsFooBar = ( superclass ) => class extends AsFoo( superclass ){
    get bar(){ return true; }
  },

  FooBar = AsFooBar( Object ),
  fb = new FooBar();

console.log( fb.foo, fb.bar );
// true, true

However when I translate them to TypeScript, I get an error with AsFoo( superclass ) .

type Constructor<T = {}> = new ( ...args: any[] ) => T;

interface Foo {
    foo: boolean;
}

interface FooBar extends Foo {
    bar: boolean;
}

const
  AsFoo = <T extends Constructor>( superclass: T ): Constructor<Foo> & T => class extends superclass implements Foo {
    get foo(){ return true; }
  },
  AsFooBar = <T extends Constructor>( superclass: T ): Constructor<FooBar> & T => class extends AsFoo<T>( superclass ) implements FooBar {
    get bar(){ return true; }
  };

// Type 'Constructor<Foo> & T' is not a constructor function type. ts(2507)

Is there something I can do to make TypeScript work with this pattern? I'd rather not just // @ts-ignore: ¯\\_(ツ)_/¯ it.

I am currently using TypeScript 3.2.4.

export type Constructor<T = {}> = new (...args: any[]) => T;
/* turns A | B | C into A & B & C */
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I
    : never;
/* merges constructor types - self explanitory */
export type MergeConstructorTypes<T extends Array<Constructor<any>>> = UnionToIntersection<InstanceType<T[number]>>;

export function Mixin<T extends Array<Constructor<any>>>(constructors: T): Constructor<MergeConstructorTypes<T>> {
    const cls = class {
        state = {};

        constructor() {
            constructors.forEach((c: any) => {
                const oldState = this.state;
                c.apply(this);
                this.state = Object.assign({}, this.state, oldState);
            });
        }
    };
    constructors.forEach((c: any) => {
        Object.assign(cls.prototype, c.prototype);
    });
    return cls as any;
}

this is an implementation i was playing around with awhile back, it also merges the states of each class but feel free to alter that part to suit your needs.

The usage is as follows...

class A {
    getName() {
        return "hello"
    }
}


class B {
    getClassBName() {
        return "class B name"
    }
}

class CombineAB extends Mixin([A, B]) {
    testMethod() {
        this.getClassBName //is here
        this.getName // is here
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM