Interface Types

 

Classes in Flow are nominally typed. This means that when you have two separate classes you cannot use one in place of the other even when they have the same exact properties and methods.

1
2
3
4
5
6
7
8
9
10
11
// @flow
class Foo {
  serialize() { return '[Foo]'; }
}

class Bar {
  serialize() { return '[Bar]'; }
}

// $ExpectError
const foo: Foo = new Bar(); // Error!
Bar This type is incompatible with Foo

Instead, you can use interface in order to declare the structure of the class that you are expecting.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// @flow
interface Serializable {
  serialize(): string;
}

class Foo {
  serialize() { return '[Foo]'; }
}

class Bar {
  serialize() { return '[Bar]'; }
}

const foo: Serializable = new Foo(); // Works!
const bar: Serializable = new Bar(); // Works!

You can also use implements to tell Flow that you want the class to match an interface. This prevents you from making incompatible changes when editing the class.

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
interface Serializable {
  serialize(): string;
}

class Foo implements Serializable {
  serialize() { return '[Foo]'; } // Works!
}

class Bar implements Serializable {
  // $ExpectError
  serialize() { return 42; } // Error!
}
number This type is incompatible with string

You can also use implements with multiple interfaces.

1
2
3
class Foo implements Bar, Baz {
  // ...
}

Interface Syntax

Interfaces are created using the keyword interface followed by its name and a block which contains the body of the type definition.

1
2
3
interface MyInterface {
  // ...
}

The syntax of the block matches the syntax of object types and has all of the same features.

Interface Methods

You can add methods to interfaces following the same syntax as object methods.

1
2
3
interface MyInterface {
  method(value: string): number;
}
Interface Properties

You can add properties to interfaces following the same syntax as object properties.

1
2
3
interface MyInterface {
  property: string;
}

Interface properties can be optional as well.

1
2
3
interface MyInterface {
  property?: string;
}
Interfaces as maps

You can create “indexer properties” the same way as with objects.

1
2
3
interface MyInterface {
  [key: string]: number;
}

Interface Generics

Interfaces can also have their own generics.

1
2
3
4
interface MyInterface<A, B, C> {
  property: A,
  method(val: B): C,
}

Interface generics are parameterized. When you use an interface you need to pass parameters for each of its generics.

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
interface MyInterface<A, B, C> {
  foo: A,
  bar: B,
  baz: C,
}

var val: MyInterface<number, boolean, string> = {
  foo: 1,
  bar: true,
  baz: 'three',
};

Interface property variance (read-only and write-only)

Interface properties are invariant by default. But you can add modifiers to make them covariant (read-only) or contravariant (write-only).

1
2
3
4
interface MyInterface {
  +covariant: number;     // read-only
  -contravariant: number; // write-only
}

Covariant (read-only) properties on interfaces

You can make a property covariant by adding a plus symbol + in front of the property name.

1
2
3
interface MyInterface {
  +readOnly: number | string;
}

This allows you to pass a more specific type in place of that property.

1
2
3
4
5
6
7
// @flow
// $ExpectError
interface Invariant {  property: number | string; }
interface Covariant { +readOnly: number | string; }

var value1: Invariant = { property: 42 }; // Error!
var value2: Covariant = { readOnly: 42 }; // Works!
string This type is incompatible with number

Because of how covariance works, covariant properties also become read-only when used. Which can be useful over normal properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @flow
interface Invariant {  property: number | string; }
interface Covariant { +readOnly: number | string; }

function method1(value: Invariant) {
  value.property;        // Works!
  value.property = 3.14; // Works!
}

function method2(value: Covariant) {
  value.readOnly;        // Works!
  // $ExpectError
  value.readOnly = 3.14; // Error!
}
property `readOnly` Covariant property `readOnly` incompatible with contravariant use in assignment of property `readOnly`

Contravariant (write-only) properties on interfaces

You can make a property contravariant by adding a minus symbol - in front of the property name.

1
2
3
interface InterfaceName {
  -writeOnly: number;
}

This allows you to pass a less specific type in place of that property.

1
2
3
4
5
6
7
8
9
// @flow
interface Invariant     {  property: number; }
interface Contravariant { -writeOnly: number; }

var numberOrString = Math.random() > 0.5 ? 42 : 'forty-two';

// $ExpectError
var value1: Invariant     = { property: numberOrString };  // Error!
var value2: Contravariant = { writeOnly: numberOrString }; // Works!
string This type is incompatible with number

Because of how contravariance works, contravariant properties also become write-only when used. Which can be useful over normal properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Invariant     {   property: number; }
interface Contravariant { -writeOnly: number; }

function method1(value: Invariant) {
  value.property;        // Works!
  value.property = 3.14; // Works!
}

function method2(value: Contravariant) {
  // $ExpectError
  value.writeOnly;        // Error!
  value.writeOnly = 3.14; // Works!
}