Repository navigation
Generic return value of a mixin constructor #22431
Description
Activity
Two things, a mixin constructor needs to have a
...argsin it's signature. this way the compiler can assert that any override the new class adds is valid; see https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#support-for-mix-in-classes for mode details. Second, the return type of the constructor needs to be something that is not generic, in this case, it is a generic constraint toInstance. I think we can relax this one, but i need to verify that this is the case.So your
MixInfunction should instead look like:interface Constructor<I extends Instance> { new(...args: any[]): I; } function MixIn<T extends Constructor<Instance>>(Base: T) { return class extends Base { } }
if you want to capture the type of the instance you can use
InstanceType<T>if you are using TS 2.8.- addedQuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
on Mar 9, 2018 thanks for your answer. In my case the Mixin is just for a special Base because the added functonality uses functions from that class. So I used the real class interface (the added functionality at my example makes no sense here, it is just to show what I mean):
function MixIn<T extends mongoose.Document, M extends mongoose.Model<T>>(Base: M) { return class extends Base { static findByName(name: string): Promise<T> { return this.find({name: name}); } }
I think that does not work because your argument " a mixin constructor needs to have a ...args in it's signature". The
mongoose.Modeldefines the constructor as:new(doc?: any): T;
I tried to write my ownModelinterface with the required methods and the...argsconstructor:interface Model<T extends mongoose.Document> { new(...args: any[]): T; // copied from @types/mongoose/index.d.ts find(callback?: (err: any, res: T[]) => void): mongoose.DocumentQuery<T[], T>; find(conditions: any, callback?: (err: any, res: T[]) => void): mongoose.DocumentQuery<T[], T>; find(conditions: any, projection?: any | null, callback?: (err: any, res: T[]) => void): mongoose.DocumentQuery<T[], T>; find(conditions: any, projection?: any | null, options?: any | null, callback?: (err: any, res: T[]) => void): mongoose.DocumentQuery<T[], T>; } function MixIn<T extends mongoose.Document, M extends Model<T>>(Base: M) { return class extends Base { static findByName(name: string): Promise<T> { return this.find({name: name}); } }
That didn't work too. But as I changed the return value of the interface to
{}instead ofT, it worked... I don't understand why it don't work withTbut it works... great thanksThe only disadvantage: I have to copy the interface to get the required interface with the
...argssignature. Is there a better solution for it?Also thanks for the hint with
InstanceType- when I change to TS 2.8 I can remove the first generic Type.I think that does not work because your argument " a mixin constructor needs to have a ...args in it's signature". The mongoose.Model defines the constructor as: new(doc?: any): T; I tried to write my own Model interface with the required methods and the ...args constructor:
You don't need to write your own
Modelinterface. It will already work with your mixin. Classes that are passed to a mixin function do not need to explicitly declare a...args: any[]signature.Example:
class Foo { constructor(a: string) {} } function MyMixin<C extends new (...args: any[]) => {}>(Base: C) { return class extends Base { mixinMethod() {} } } class Bar extends MyMixin(Foo) {} }
You're right, your example works. But you've removed my requirement, that I want to call a function of the Base class. To show it, I add it to your example:
class Foo { constructor(a: string) {} doSomething() {} } function MyMixin<C extends new (...args: any[]) => {}>(Base: C) { return class extends Base { mixinMethod() { this.doSomething(); // ERROR Property 'doSomething' does not exist on type '(Anonymous class)'. } } } class Bar extends MyMixin(Foo) {}
Now I get an error because the
doSomethingis unknown - that's correct. I could add the required function to the Base class declaration:C extends new (...args: any[]) => {doSomething(): void}
But now I'm rewriting the model interface, because a
C extends Foois not working.Xenya0815 You shouldn't do
C extends Foo, you should doC extends Constructor<Foo>.function MyMixin<C extends new (...args: any[]) => Foo>(Base: C) {Does that work?
ahh, you got me! you're right with instance methods. That works like a charm.
But I thought already about to do the example like my example in my previous post, with static methods.
But perhaps you have for that approach a same nice answer :-)so once again:
class Foo { static doSomething() {} constructor(a: string) {} } function MyMixin<C extends new (...args: any[]) => {}>(Base: C) { return class extends Base { static mixinMethod() { this.doSomething(); // Property 'doSomething' does not exist on type '{ new (...args: any[]): (Anonymous class); prototype: MyMixin<any>.(Anonymous class); mixinMethod...' } } } class Bar extends MyMixin(Foo) {}
When I change the base class declaration to:
C extends { new(...args: any[]): {}; doSomething(): void }
It works again. Do you have a better solution for here without to define the required methods manually?
Xenya0815 How about doing
Base.doSomething();Does that work? EDIT: Nevermind, that doesn't make sense.You can do
Foo.doSomething();Sure, I could use the Base directly like
Foo.doSomething(). But with that I loose the possibility to use an already extended/overwritten method.class Foo { static doSomething() {} constructor(a: string) {} } class ExtendedFoo extends Foo { static doSomething() { console.log('doSomething was called'); super.doSomething(); } } function MyMixin<C extends { new(...args: any[]): {}; doSomething(): void }>(Base: C) { return class extends Base { static mixinMethod() { this.doSomething(); // with log Foo.doSomething(); // without log } } } class Bar extends MyMixin(ExtendedFoo) {}
Xenya0815 I see what you mean; that's a bummer. I devised a weird hack, does this work?
function MyMixin<C extends typeof Foo>(Base: C) { return (<C2 extends Constructor<Foo>>(_base: C2) => { return class extends _base { static mixinMethod() { Base.doSomething(); // with log Foo.doSomething(); // without log } } })(Base); }
Baseis constrained to have all the static methods, and_baseis constrained to be a valid constructor. You have to call static methods asBase.doSomething(), but this lets you use the implementations ofExtendedFoo.typescript-bot commented
on Apr 10, 2018 ContributorMore actionsAutomatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
- locked and limited conversation to collaborators
on Jul 25, 2018
TypeScript Version: 2.8.0-dev.20180308
Code
Expected behavior:
That my MixIn accepts that construct so that I can define a extended interface T with properties which are available at a instance of the MixIn class.
Actual behavior:
It works when I remove the generic type at the function
MixIn(likefunction MixIn(Base: Constructor<Instance>)) .Related Issues:
I found old issues like #8853 or #4890
Because that I thought it should work.