Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics and extending an abstract class #25606

Closed
mshoho opened this issue Jul 12, 2018 · 9 comments
Closed

Generics and extending an abstract class #25606

mshoho opened this issue Jul 12, 2018 · 9 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@mshoho
Copy link
Member

mshoho commented Jul 12, 2018

Hi, I want to have a function (with generics) which takes an abstract class and returns a derived class with the implemented abstract methods.

The example of what I have now:

interface ComponentConstructor<T, P> {
  new (props: P, context?: any): T;
}

type GetSomething<R, P> = (props: P) => R;

// The function.
function extend<P, R, T extends React.Component<P>>(Component: ComponentConstructor<T, P>, getSomething: GetSomething<R, P>) {
    return class extends (Component as React.ComponentClass<P>) {
        getSomething(): R {
            return getSomething(this.props);
        }
    }
}

interface MyProps {
    prop1: string;
    prop2: number;
    prop3: boolean;
}

interface MySomething {
    prop4: string;
    prop5: string;
}

// How I have it now.
class MyClass extends React.Component<MyProps> {
   // I want to have this as abstract method.
   getSomething!: (props: MyProps) => MySomething;

   render() {
        const something = this.getSomething(this.props);
        return <div>{ something.prop4 + ' ' + something.prop5 }</div>;
    }
}

function getMySomething(props: MyProps): MySomething {
    return { prop4: 'hello ' + props.prop1, prop5: 'world' };
}

const MyDerivedClass = extend(MyClass, getMySomething);

// This works ok.
<MyDerivedClass prop1={ 'wonderful' } prop2={ 1 } prop3 />

But it allows to instantiate MyClass and I'd like to have it like this:

abstract class MyClass extends React.Component<MyProps> {
   abstract getSomething: (props: MyProps) => MySomething;

   render() {
        const something = this.getSomething(this.props);
        return <div>{ something.prop4 + ' ' + something.prop5 }</div>;
    }
}

Is there a way to make it work?

@ghost
Copy link

ghost commented Jul 12, 2018

To get a mixin to work properly, you need to use a generic type for the class that extends { new(...args: any[]): any }. Like this:

function extend<P, R, Cls extends { new(...args:any[]): any }>(Component: Cls, getSomething: GetSomething<R, P>) {
    return class extends Component {
        getSomething(): R {
            return getSomething(this.props);
        }
    }
}

We really need to work on microsoft/TypeScript-Handbook#574.

@mshoho
Copy link
Member Author

mshoho commented Jul 12, 2018

@andy-ms { new(...args: any[]): any } also doesn't work with the abstract classes...

@ghost
Copy link

ghost commented Jul 12, 2018

This appears to be possible but it's ugly:

abstract class AbstractConstructorExample {}
type AbstractConstructable = typeof AbstractConstructorExample;

interface Extended {
    getSomething(): number;
}
function extend<Cls extends AbstractConstructable>(cls: Cls): Cls & { new(): Extended } {
    return class extends (cls as any) {
        getSomething() {
            return 0;
        }
    } as any;
}

abstract class MyAbstractClass { a() { return 0; } }
const SubClass = extend(MyAbstractClass);

new SubClass().getSomething();

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jul 12, 2018
@mshoho
Copy link
Member Author

mshoho commented Jul 12, 2018

@andy-ms almost, but if it's an abstract class with generics... :)

If I have abstract class AbstractConstructorExample<P> extends React.Component<P> {}, I cannot do type AbstractConstructable<P> = typeof AbstractConstructorExample<P>.

Thanks for helping me out!

@mhegazy
Copy link
Contributor

mhegazy commented Jul 12, 2018

Looks like #17572

@mshoho
Copy link
Member Author

mshoho commented Jul 17, 2018

@mhegazy yep, seems like. But the question is is typeof for a class with generics (typeof AbstractClass works, but typeof AnotherAbstractClass<P> doesn't let to pass this P) present somewhere on radar?

My use case for it is a mixin which adds mandatory methods to the class which is being extended.

I'm building a semantic accessibility API and I have a base class like AccessibleView. I want to have an abstract method in this class like abstract getAccessibilityProps(): P. And the mixin should work like extend(AccessibleView, Checkbox), extend(AccessibleView, ModalContainer) and so on. It should implement getAccessibilityProps(). Right now I do it like getAccessibilityProps!: () => P in the base class and I can't mark it abstract, which allows to instantiate the base class which makes no sense in this case.

@mshoho
Copy link
Member Author

mshoho commented Jul 17, 2018

In other issues I saw requests for something like interface AbstractConstructor<P> { abstract new(...): P }. I guess that would do as well.

@mhegazy
Copy link
Contributor

mhegazy commented Jul 17, 2018

typeof AnotherAbstractClass<P> not sure what that means really.
AnotherAbstractClass refers to the type of an instance of class AnotherAbstractClass.
typeof AnotherAbstractClass refers to the type of a the constructor function for AnotherAbstractClass, that function is a generic function that takes arguments of type P and return AnotherAbstractClass<P>, in other words the P is not a function of the constructor, but rather of its invocation.

the fix as tracked by #17572, is to allow adding abstract to a construct signatures, so that you can have a type { abstract new (): Instance; } which an abstract class would be assignable to, and the constructor would not be callable.

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants