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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With