Class types
Flow supports classes and mostly in an automatic way. Every time you define a class, it becomes a type by itself, so you don't have to do anything else; you can just use it elsewhere. (We'll be seeing more about classes in a short while, in the Working with Objects and Classes section.) You can assign types to properties and methods in the same way as for objects and functions. Using our Person class again as an example, the following code shows how to define it with Flow:
// Source file: src/types_advanced.js
class Person {
// class fields need Flow annotations
first: string;
last: string;
constructor(first: string, last: string) {
this.first = first;
this.last = last;
}
initials(): string {
return `${this.first[0]}${this.last[0]}`;
}
fullName(): string {
return `${this.first} ${this.last}`;
}
get lastFirst(): string {
return `${this.last}, ${this.first}`;
}
set lastFirst(lf: string) {
// very unsafe; no checks!
const parts = lf.split(",");
this.last = parts[0];
this.first = parts[1];
}
}
let pp = new Person("Jan", "Jansen"); // OK
let qq = new Person(1, 2); // error: wrong types for the constructor
let rr: Person; // OK, "Person" type is understood and can be used
However, there is a problem you may encounter. If you have distinct classes, even with exactly the same shape, they won't be considered equivalent by Flow. For instance, even if Animal and Pet are equivalent, the assignment of Pet to Animal (or vice versa) won't be allowed:
// Source file: src/types_advanced.js
class Animal {
name: string;
species: string;
age: number;
}
class Pet {
name: string;
species: string;
age: number;
}
let tom: Animal;
tom = new Pet(); // error: Pet and Animal are distinct types
In this particular case, if you were to say that Pet extends Animal, then you could assign Pet to Animal, but not the other way round. A more general solution would involve creating an interface and using it in several places:
// Source file: src/types_advanced.js
interface AnimalInt {
name: string;
species: string;
age: number;
}
class Animal2 implements AnimalInt {
name: string;
species: string;
age: number;
}
class Pet2 implements AnimalInt {
name: string;
species: string;
age: number;
}
let tom2: AnimalInt; // not Animal2 nor Pet2
tom2 = new Pet2(); // OK now
Note that the interface definition, which includes three fields, doesn't exempt you from declaring those fields when you define Animal2 or Pet2; in fact, if you were to forget some of these fields, Flow would point out the error, because neither of the three is marked as optional.