Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.
In this article, we’ll look at how to add Flow types to arrays and tuples.
Array Definition
We define arrays exactly like in JavaScript. To define an array, we can use the literal:
let arr = [1, 2, 3];
We can also use the Array
constructor as follows:
new Array(3);
Array
constructor with one argument will create an empty array with the given length, so new Array(3)
returns:
[empty × 3]
Calling the Array
constructor with more than one argument creates an array with the arguments as the content, so:
new Array('foo', 'bar')
will get:
["foo", "bar"]
We can also add array entries after it’s defined:
let arr = [];
arr[0] = 'foo';
Array
Type
We can type arrays by using the Array<Type>
syntax, where Type
is any type that we want the array entries to be.
For example, we can define a number array by writing:
let arr: Array<number> = [100, 200];
We can type an array with different types of values by using the mixed
type as the type. For instance, we can write:
let arr: Array<mixed> = [1, true, "foo"];
Array
Type Shorthand
We can use the Type[]
syntax as a shorthand for typing array. For example, we can write:
let arr: number[] = [100, 200];
to declare a number array.
We can declare an array that can be null
by putting a question mark before the type name. For example, we can write:
let arr: ?number[] = null;
Also, we can assign a number array to it:
let arr: ?number[] = [1, 2];
If we want an array to take on null
values in addition to values with the given type, we can put parentheses around the type name. For example, if we want a numerical array that can have null
entries, we can write:
let arr: (?number)[] = [1, 2, null];
Accessing Array Values
Flow doesn’t check whether an array entry is defined before we access it. Therefore, we can run into undefined values at runtime, which may cause errors.
For instance, we can write:
let arr: number[] = [1, 2];
let num = arr[3];
We should check if entries are undefined
before accessing them.
Read Only Arrays
We can define read-only arrays by writing $ReadOnly<T>
, where T
is the placeholder for the type that we want the array values to take on.
For example, we can define a numerical read-only array by writing:
const arr: $ReadOnlyArray<number> = [100, 200];
We can only read the values from the arr
array.
Also, read-only arrays can take on object types. For example, we can write:
const arr: $ReadOnlyArray<{foo: number}> = [{foo: 1}, {foo: 2}];
In the case of object entries, we can modify the property value of the object’s existing properties as follows:
arr[0].foo = 2
Note that read-only arrays have subtypes of a union type. For example, we $ReadOnlyArray<number>
is a subtype of $ReadOnlyArray<number | string>
.
However, Array<number>
isn’t a subtype of Array<number | string>
.
For example, we can write:
const foo = (arr: $ReadOnlyArray<number | string>) => {
}const array: Array<number> = [1]
foo(array)
In the code above, array
will be converted to a $ReadOnlyArray<number | string>
as it’s passed in to foo
. This is because read-only arrays can’t have its entries modified to types that aren’t specified, so Flow allows it to be passed in.
Tuples
Tuples are lists with a small number of items. In JavaScript, we create a tuple with arrays.
With Flow, we have a tuple type to type these kinds of arrays. For example, we can write:
let tuple: [string, boolean] = ['foo', true];
Then the first entry always is a string and the second is always a boolean.
We can access entries by its index, so given the tuple that we have above, we can write:
let str : string = tuple[0];
We can’t access indexes that aren’t defined in the tuple. For example, the following code will fail:
let str : string = tuple[3];
since we don’t have 4 or more elements in our tuple.
Flow doesn’t know the type of value we try to access, so whatever the value is will have the union of all the types in the tuple.
For example, the value of tuple
we have above will be of type string | boolean
:
let val: string | boolean = tuple[0];
When we set new values to a tuple entry, it must match the type of that’s defined in that location. For example, if we have:
let tuple: [number, boolean, string] = [1, false, "foo"];
Then whatever we assign to tuple[0]
must be a number, tuple[1]
must be a boolean, and tuple[2]
must be a string.
Strictly Enforced Length
Lengths of tuples are strictly enforced, so we can assign a tuple of one length to one with different length.
For example, the following assignment will fails since tuple1
and tuple2
have different lengths:
let tuple1: [number, boolean, string] = [1, true, 'foo'];
let tuple2: [number, boolean] = tuple1;
Tuples aren’t Arrays
In Flow, tuples aren’t arrays even though they look the same. We can’t use arrays methods with tuples and we can’t assign arrays to tuples regardless of length.
For example, the following will fail:
let array: number[] = [10, 20];
let tuple: [number, number] = array;
Assigning tuples to arrays also doesn’t work. For example:
let tuple: [number, number] = [1, 2];
let arr: number[] = tuple;
will fail.
Array methods like join
or push
can’t be used on tuples, so:
tuple.push(3);
tuple.join(',');
will give errors.
Arrays and tuples are lists of items. They aren’t the same. Arrays can use array methods. Also, we specify the type for all entries at once. With tuples, we define the type for each entry. Tuples also can’t use array methods.
Arrays can have null
entries if we add a question mark before the type name and parentheses around it. An array can be null
if we skip the parentheses and keep the rest.