When do you use an interface over a type alias in flow?
This is a great question. Ideally there would be no difference between an interface and an object type. As implemented, there are a handful of (often subtle) differences between them.
The biggest difference is that Flow considers methods declared on an interface to be "read-only." This allows subtypes to be covariant w.r.t. methods, which is a very common pattern with inheritance hierarchies.
In time, I would like to see Flow unify these concepts, but until then here's my rule of thumb for choosing between interfaces and object types:
- Use object types to describe bags of mostly data that are passed around in your app, e.g., props/state for React components, Flux/Redux actions, JSON-like stuff.
- Use interfaces to describe service-like interfaces. Usually these are mostly methods, e.g., Rx.Observable/Observer, Flux/Redux stores, abstract interfaces. If a class instance is likely to be an inhabitant of your type, you probably want an interface.
Hope this helps!
Interfaces in Flow can be used to make sure a class implements certain methods and properties. For instance:
interface IFly {
fly(): string
}
// Good!
class Duck implements IFly {
fly() {
return "I believe I can fly"
}
}
// Bad! Cannot implement `IFly` with `Duck` because number is incompatible with string in the return value of property `fly`.
class Duck implements IFly {
fly() {
return 42
}
}
// Bad! Cannot implement `IFly` with `Duck` because property `fly` is missing in `Duck` but exists in `IFly`.
class Duck implements IFly {
quack() {
return "quack quack"
}
}
However, if we define an equivalent IFly
type our Duck
class won't be able to implement it:
type IFly = {
fly(): string
}
// Cannot implement `Fly` because it is not an interface.
class Duck implements Fly {
fly() {
return "I believe I can fly"
}
}
In addition, there are more subtle differences between types and interfaces.
By default, interface properties are invariant. For example:
interface Foo {
property: string | number
}
let foo: Foo = { property: 42 } // Cannot assign object literal to `foo` because number is incompatible with string in property `property`.
To make interface property covariant they need to be made read-only:
interface Foo {
+property: string | number
}
let foo: Foo = { property: 42 } // Good!
With types, on the other hand, Flow will not complain:
type Foo = {
property: string | number
}
// Good!
let foo: Foo = { property: 42 }
References:
- Flow interfaces
- Type variance