I'm facing a problem with Facebook Flow, briefly stated in the title of the question. The reason is that I don't want to specify Class<Bird> | Class<Snake>
100 times.
Here is a simplified example:
There are many classes which all implement a particular interface Animal
. I need to be able to specify, that, let's say, a function accepts values of type T
and those values are classes which implement Animal
. That is if I call return new c();
in the function (as in the code snippet below), its return type will be T
, an instance of a class which implements Animal
;
Currently, I'm getting this error. But I've tried numerous other approaches: type assertions, object types with intersection operators etc. Those didn't work either.
//@flow strict
interface Animal {
eat(): void,
}
class Dog implements Animal {
constructor() { }
eat() { }
bark() { }
}
foo(Dog);
function foo(c: Class<Animal>) {
return new c(); ■ Cannot call `c` because property `constructor` is missing in `Animal` [1].
}
Is this possible to do? Even if it will be to skip somehow type checks in this particular place.
I'm facing a problem with Facebook Flow, briefly stated in the title of the question. The reason is that I don't want to specify Class<Bird> | Class<Snake>
100 times.
Here is a simplified example:
There are many classes which all implement a particular interface Animal
. I need to be able to specify, that, let's say, a function accepts values of type T
and those values are classes which implement Animal
. That is if I call return new c();
in the function (as in the code snippet below), its return type will be T
, an instance of a class which implements Animal
;
Currently, I'm getting this error. But I've tried numerous other approaches: type assertions, object types with intersection operators etc. Those didn't work either.
//@flow strict
interface Animal {
eat(): void,
}
class Dog implements Animal {
constructor() { }
eat() { }
bark() { }
}
foo(Dog);
function foo(c: Class<Animal>) {
return new c(); ■ Cannot call `c` because property `constructor` is missing in `Animal` [1].
}
Is this possible to do? Even if it will be to skip somehow type checks in this particular place.
Share Improve this question edited Mar 17 at 16:47 jabaa 6,9983 gold badges15 silver badges39 bronze badges asked Mar 17 at 16:30 d.kd.k 4,4702 gold badges32 silver badges41 bronze badges 7- I don't see what's the problem. A type that represents many types could be 1) the common base class of all those types, 2) some interface type if all the types in question implement this interface. Everything else depends on what you are going to do with this type. The usual approach is to use this type as a compile-time (abstraction) type to handle a polymorphic set of instances of your many types. And this is the very heard of OOP. – Sergey A Kryukov Commented Mar 17 at 17:06
- Another hint: fet generics and add a static factory method to a common base class. This factory method should call a constructor in a type-agnostic way. This is like saying: create an instance of your class, whatever it is. – Sergey A Kryukov Commented Mar 17 at 17:09
- @SergeyAKryukov thank you for the reply. May I ask you what did you mean by the 'type agnostic way'? It's not clear, how to do it – d.k Commented Mar 17 at 17:16
- The problem with the base class approach, as I understand, is that you can't use it as a type representing all other classes. Flowtype will treat it as an instance of that base class, not an instance of any of its child classes. – d.k Commented Mar 17 at 17:29
- Wrong. The base class does represent all derived classes, and this is the very heart of OOP and its major point. In particular, do you know how late binding works? – Sergey A Kryukov Commented Mar 17 at 17:56
2 Answers
Reset to default 0My current solution is to ignore that error.
And my conclusion is that it's not only justified in this situation, but it's the correct way. Because the Flow documentation explicitly mentions, that Flow is not expected to handle every possible syntax construct (sounds pretty reasonable, since it's main benefit seems to be to statically define certain "contracts" between parts of an application, not to be a sort of "static runtime" engine for types).
Flow reports many different kinds of errors for many common programming mistakes, but not every JavaScript pattern can be understood by Flow.
So in my case I just added the comment and declared the return type:
function foo(c: Class<Animal>): Animal {
//$FlowExpectedError[prop-missing]
return new c();
}
In Facebook Flow, the issue arises because Class<Animal>
suggests that Animal
should be instantiated, but interfaces in Flow don't have a constructor. You need to ensure that the class type parameter correctly extends Animal
while keeping the constructor accessible.
Corrected Code
// @flow strict
interface Animal {
eat(): void,
}
class Dog implements Animal {
constructor() {}
eat() {}
bark() {}
}
function foo<T: Animal>(c: Class<T>): T {
return new c();
}
const dogInstance = foo(Dog);
dogInstance.eat(); // Works fine
Explanation
T: Animal
ensures thatT
is a subtype ofAnimal
.Class<T>
ensures thatT
is a class constructor.return new c();
now correctly returns an instance of the provided class.
This approach avoids repeating multiple class types (Class<Bird> | Class<Snake>
etc.) and ensures proper type safety.
For reference, Microsoft primarily focuses on TypeScript rather than Flow. However, you can check Flow's official documentation for Class<T>
and type constraints:
Reference:
Flow Docs - ClassFlow Docs - Generics