Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't you use the types of TypeScript abstract classes to create React factories, when non-abstract classes work fine?

The following Typescript code using React compiles and works just fine:

import * as React from "react";

class MyComponent<P> extends React.Component<P, any> {
    static getFactory() {
        return React.createFactory(this);
    }
}

However, the following code, similar in every way except the class is now abstract, throws an error at compilation, on the line where the factory is created:

import * as React from "react";

abstract class MyComponent<P> extends React.Component<P, any> {
    static getFactory() {
        return React.createFactory(this);
    }
}

The error is:

error TS2345: Build:Argument of type 'typeof MyComponent' is not assignable to parameter of type 'ComponentClass<any> | StatelessComponent<any>'.

I can think of no reason why it should make a difference whether the class is marked as abstract or not.

Any insights as to why this might be the case?

================ADDED 9/22/2016=============

In my example above, I probably should have called MyComponent something like MyBaseComponent. The whole point here is that I am creating a framework component that I never want instantiated. I only want things derived from MyComponent to ever be instantiated. (Note, for example, that MyComponent doesn't even have a render() method declared.)

So.... I declare a really-to-be-instantiated class as:

interface MyDerivedComponentProps {
    id: string;
}

class MyDerivedComponent extends MyComponent<MyDerivedComponentProps> {
    render() {
        return /* Some JSX rendering stuff here */
    }
}

Note that this all works just fine.... if I don't ever instantiate MyComponent but do instantiate MyDerivedComponent. But the simple act of decorating MyComponent with the abstract keyword, which ought to serve to ensure that MyComponent will never be directly instantiated causes this compiler error. And I'm still of the opinion that it shouldn't. The compiler is handling the inheritance just fine if the base class is not marked as abstract. It should also handle it fine if the base class is marked as abstract, or perhaps not, but if not, why?

================ADDED 9/29/2016=============

As per request in comments, here is a complete example code showing both declarations and inheritance for both:

import * as React from "react";
import * as ReactDOM from "react-dom";

interface MyDerivedComponentProps {
    id: string;
}

// WORKING EXAMPLE
class MyBaseNonAbstractComponent<P> extends React.Component<P, any> {
    static getFactory() {
        return React.createFactory(this);  // NO COMPILER ERROR HERE
    }
}

class MyDerivedFromNonAbstractComponent extends MyBaseNonAbstractComponent<MyDerivedComponentProps> {
    render() {
        return <div>Hello from id {this.props.id}</div>;
    }
}

// NOT WORKING EXAMPLE
abstract class MyBaseAbstractComponent<P> extends React.Component<P, any> {
    static getFactory() {
        return React.createFactory(this); // HERE IS THE COMPILER ERROR
    }
}

class MyDerivedFromAbstractComponent extends MyBaseAbstractComponent<MyDerivedComponentProps> {
    render() {
        return <div>Hello from id {this.props.id}</div>;
    }
}

ReactDOM.render(MyDerivedFromNonAbstractComponent.getFactory()({ id: "idForNonAbstract" }), document.getElementById("managed-content-for-non-abstract"));

ReactDOM.render(MyDerivedFromAbstractComponent.getFactory()({ id: "idForAbstract" }), document.getElementById("managed-content-for-abstract"));

================ADDED 9/29/2016 #2=============

It is definitely the case that whether or not a base class is marked abstract, if it has a static method on it, and that method refers to this, then it will actually dereference to the constructor of the derived class used to invoke the method.

So, if we have the following:

class BaseConcrete {
    static logWhoAmI() {
        console.log(`this is ${this}`);
    }
}

class DerivedConcrete extends BaseConcrete {
}

DerivedConcrete.logWhoAmI(); // This will log constructor of DerivedConcrete

abstract class BaseAbstract {
    static logWhoAmI() {
        console.log(`this is ${this}`);
    }
}

class DerivedAbstract extends BaseAbstract {
}

DerivedAbstract.logWhoAmI(); // This will log constructor of DerivedAbstract

So it appears that Typescript does not treat this referred to in a static method of a base class any differently when that base class is abstract or not.

It is only, somehow, the signature of the React.createFactory() method that seems to get confused somehow when passed the "this". It complains because on the face of it, it seems as if it is being passed a constructor for an abstract method, which would of course be illegal. But since the class itself is abstract, the this at runtime is guaranteed to be a derived class, and React.createFactory should work just fine. In fact, as one commenter pointed out, if you cast the argument to React.createFactory to any, it will work fine at runtime. But this is hack it seems to me. Appreciate the discussion this far though.

like image 335
Stephan G Avatar asked Sep 14 '25 18:09

Stephan G


1 Answers

Abstract classes cannot be directly instantiated with new (that's why they are called so). Thus, you can't use them as React components. They can be the base classes for other components, though.

And that's the thing TypeScript compilers checks statically. Since abstract class doesn't have new operator, it cannot be coerced to ComponentClass.

like image 183
gaperton Avatar answered Sep 17 '25 09:09

gaperton