1

I have the following problem i have the function "Foo" typed as the following.

function Foo(...cons: any[]) {
    return function (this: {inject: any}) {
            return (bar: any) => {
                this.onChange  // notice here how i have access to onChange
                return reactC;
        }
    }
}

The above code is almost what i want basically i'm hoping the "this" type iv'e added will be able to be used inside "bar" (ignore the 'any's those are the types i can easily get, i just want the "this" to be properally typed.

Implemented it looks like this.

export const TestImplementation: typeof Foo = Foo([Input])()(class TestClass {
    testFunction() {
        //this.inject
    }
});

// the "this" context of type 'void' is not assignable to the methods 'this' of type 'any'

and 'this.inject' isn't present.

Don't think my implementation is right but looking for any way to alter the type of 'this' within a class https://preview.tinyurl.com/ycc9c4l5 < safe link to the playground with this problem

Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39
  • It's not very clear to me what you are trying to achive. From what I gather, you want to `this` for `TestClass ` to be decided by what is passed to `Foo` ? – Titian Cernicova-Dragomir Aug 20 '18 at 22:08
  • inside TestClass i want "this" to have added to it whatever types come from the "this" type in Foo. However i'm willing to settle for any possible way at all to change the type of "this" inside the class to add some properties dynamically – Shanon Jackson Aug 20 '18 at 22:14

2 Answers2

1

It's not very clear from the question what you want to achieve, but from my understanding you want the type of this to be modified by the Foo function for the class that is passed as an argument.

You can't alter the type of this for all members of a class except through inheritance. You can however change the type of this for an object literal that is passed to a function by using ThisType<T>. ThisType<T> is a special marker for the compiler (it's magic) and the compiler will use T as the type of this for any object literal that is assigned to ThisType<T>.

function Foo<T>(...cons: T[]) {
    // Infer the type of the object literal passed in in TClass
    // use ThisType to let the compiler know this should contain 
    // all memebers of the object literal plus a merger of all memebrs passed in to cons  
    return function <TClass>(classMember: TClass & ThisType<TClass & UnionToIntersection<T>>) : TClass & UnionToIntersection<T> {
        return null as any; // implementation   
    }
}

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never



class FakeClass {
    inject: string; // ignore this property doesn't matter
}

const TestFoo = Foo(new FakeClass())({
    TestTypes() {
        this.inject // this has the members of FakeClass
    }
})

TestFoo.TestTypes();

Note This solution needs noImplicitThis: true to work. Also UnionToIntersection is taken from here

Another option if you want to use a class, is to focus on the inheritance part. We can create a type to act as a fake base class for the class you pass in to Foo. You can't pass in the class directly, you will need to pass in a function which will be called by Foo with the base class to be used for the new class.

function Foo<T>(...cons: T[]) {  
    return function <TClass>(classMember: (base: Constructor<UnionToIntersection<T>>) => Constructor<TClass>) : Constructor<TClass> {
        return classMember(class {
            constructor() {
                Object.assign(this, ...cons);
            }
        } as any)   
    }
}

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

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

class FakeClass {
    inject: string;
}

const TestFoo = Foo(new FakeClass())(b=> class extends b {
    TestTypes() {
        this.inject
    }
})

new TestFoo().TestTypes(); 

Edit

From what we discussed in comments you want the result to be a class and take in other classes to act as mixins. We can modify solution 1 to do this

function Foo<T extends Constructor<any>[]>(cons: T) {  
    return function <TClass>(classMember: TClass & ThisType<TClass & MergeConstructorTypes<T>>): Constructor<TClass & MergeConstructorTypes<T>> {
        // Naive implementation, you might want to do better here 
        var cls = class { 
            constructor() {
                cons.forEach(c => c.apply(this));
            }
        };
        cons.forEach(c => Object.assign(cls.prototype, c.prototype));
        Object.assign(cls.prototype, classMember);
        return cls as any; 
    }
}

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

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type MergeConstructorTypes<T extends Constructor<any>[]> =
    UnionToIntersection<InstanceType<T[number]>>;

class FakeClassA {
    inject: string = "A"; // ignore this property doesn't matter
    logA() {
        console.log(this.inject);
    }
}

class FakeClassB {
    inject2: string = "B"; // ignore this property doesn't matter
    logB() {
        console.log(this.inject2);
    }
}


const TestFoo = Foo([FakeClassA, FakeClassB])({
    TestTypes() {
        console.log(this.inject) 
        console.log(this.inject2)
        this.logA();
        this.logB();
    }
})

new TestFoo().TestTypes();

Note The faked base class solution can also work in this case as well, but you expressed interest in the fist solution so I exemplified it for that case. Also the solution above uses 3.0 features and again requires noImplicitThis:true

Hope it helps :)

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • This was an extremely fast response, iv'e been coding typescript for 2.5 years or so and had no idea ThisType was in the standard library. going to keep it short, 1: can Foo work without instantiating new FakeClass? 2: can Foo work taking multiple classes as first argument and appending their "this" to the final class? EDIT: also solution 2 what i was looking for thank you – Shanon Jackson Aug 20 '18 at 22:37
  • and finally the actual implementation: i want to create a "correctly typed" mixin function that takes multiple classes and appends their prototype to the class, i need this to be correctly typed so that when i type "this" in the final class it should have all the prototype methods of the classes passed in. EDIT: thanks so much – Shanon Jackson Aug 20 '18 at 22:40
  • 1
    @ShanonJackson Lots of things there, `ThisType` is pretty esoteric, I happened to come across it answering another question on SO. 1 &2 . Sure, you can take in constructors, and you can take in more types I'll add a sample. – Titian Cernicova-Dragomir Aug 20 '18 at 22:45
  • Finally my fault aswell this is going to get complicated :( but i need the type of the first parameter to be a tuple of classes to mixin to the final class, and the output classes 'this' to simulate all of the classes combined. if thats even possible haha, thanks so much for help – Shanon Jackson Aug 20 '18 at 22:47
  • @ShanonJackson added a modified version, hope it's closer to what you want. – Titian Cernicova-Dragomir Aug 20 '18 at 22:57
  • Sorry Titian is it even possible to have the argument itself as a class? not as an object – Shanon Jackson Aug 20 '18 at 23:03
  • @ShanonJackson Oh, I misunderstood, I though you were asking about the mixins being classes not instances of the class. You can't change the `this` of a class directly (I state that in the second paragraph of the answer). The `ThisType` type only works for object literals. If you want a class, using the second approach I highlight is your best bet. – Titian Cernicova-Dragomir Aug 20 '18 at 23:07
  • Ok thanks Titian for all the help, i actually needed the "union" -> "intersection" type for another part of my codebase aswell so you've double helped me :) thanks so much – Shanon Jackson Aug 20 '18 at 23:08
  • @ShanonJackson the uniontointersection type is not my work of art, it belongs to jcalz, don't forget to give him an upvote on the linked answer :) – Titian Cernicova-Dragomir Aug 20 '18 at 23:10
  • Added a final solution of what i ended up with at the bottom, don't know if you use React but if you do the end result is pretty cool, avoids 100000's of function duplication across many parent components – Shanon Jackson Aug 21 '18 at 03:40
0

Hello all who come to see this in the future, thanks to Titian's answer earlier about "this" only being able to come from inheritance or manipulating ThisType which wasn't an option for me. I have built this.

export class InputMixin {
    state = {
        value: "asdsasads"
    }
    onChange(this: React.Component<any>, e: React.ChangeEvent<HTMLInputElement>) {
        e.persist();
        console.log(this, "THIS BINDING");
        this.setState((prevState: any) => ({value: e.target.value}))
    }
}

export class TestMixin {
    state = {
        myvalue: "myvalue"
    }
}

export type Constructor<T = {}> = new (...args: any[]) => T;
function Mixin<T extends Constructor<any>[]>(constructors: T): Constructor<MergeConstructorTypes<T>> {
    var cls = class {
        state = {
        }
        constructor() {
            constructors.forEach(c => {
                const oldState = this.state;
                c.apply(this);
                this.state = Object.assign({}, this.state, oldState);
            });
        }
    } as any;
    constructors.forEach((c: any) => {
        Object.assign(cls.prototype, c.prototype);
    });
    return cls as any;
}
type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type MergeConstructorTypes<T extends Constructor<any>[]> =
    UnionToIntersection<InstanceType<T[number]>>;


interface IProps {
    bleet: string;
}
export class CounterTest extends Mixin([TestMixin, InputMixin, React.Component as React.ComponentClass<IProps, any>]) {
    constructor(props: any) {
        super(props);
        this.state = {
            ...this.state,
            newState: ""
        };
        this.onChange = this.onChange.bind(this);
    }

    render() {
        return <Input value={this.state.value} onChange={this.onChange}/>
    }

The usage of this is now you can define "mixins" separate to your components and essentially use their code, this stops unlimited duplication of really basic functions that comes from the principle of having everything controlled by a parent. You no longer need onChange() on every parent that needs a child input you can simply just mixin a InputMixin and have everything typed for you, everything already there. By simplying sucking everything off the prototypes and rebinding it to another class.

Also it will merge all "states" together into a final state so multiple mixins can implement "state" and the final class will get the states of all the mixins correctly typed.

Thanks Gitter and thanks Titian.

Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39