Flow is a type checker made by Facebook for enforcing data types in JavaScript. 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 some data types unique to Flow, including maybe types, literal types, mixed
and any
types
Maybe Types
Maybe types are for annotating variables or parameters that are optional. It’s indicated by a question mark in front of the type name.
For example, ?string
and ?number
are maybe types for string and number respectively.
With ?string
type, we can set it with a string, null
, undefined
, or nothing. For example, we can write:
let a: ?string = null;
let b: ?string = undefined;
let c: ?string;
let d: ?string = 'abc';
The rules are the same for parameters:
maybeString(null);
maybeString(undefined);
maybeString();
maybeString('abc');
Anything else, like:
maybeString(0);
will get us errors.
Optional Object Properties
We can mark an object property as optional by adding a ?
after the property name.
For example, we can write:
let x: { foo?: string } = {};
let y: { foo?: string } = { foo: 'abc' };
This rule also applied to parameters, so the following code will work:
function optionalFoo(val: { foo?: string }){}
optionalFoo({});
optionalFoo({ foo: 'abc'});
undefined
also works. For example, we can write:
let y: { foo?: string } = { foo: undefined };
We can also write:
function optionalFoo(val: { foo?: string }){}
optionalFoo({ foo: undefined });
Any other values for the foo
property will result in an error.
Default Function Parameter Values
Like JavaScript since ES2015 or later, we can assign default values to function parameters. For example, we can write:
function foo(value: string = "foo") {}
The default value for the value
parameter is 'foo'
.
We can then pass in a string, undefined
or nothing to the foo
function. For example, the following will work:
foo("bar");
foo(undefined);
foo();
Passing in undefined
or nothing will result in 'foo'
being the value for the value
parameter.
Everything else passed in will result in an error. For example, the following won’t work:
foo(null);
Literal Types
Flow has literal types to let us create variables or function parameters that can only be assigned a few possible values.
For example, we can write:
let x: 1|2 = 1;
Since x
has the type 1|2
which means that x
can only be assigned the value 1 or 2. Each possible value is separated by the |
symbol. The |
symbol is the union type operator. It indicates the type is either of the types indicated in the operands.
If we write:
let y: 1|2 = 3;
We get an error since x
has been assigned a value that’s not in the list of possible values for y
.
This is the same for function parameters. For example, the following will work:
function fruitFn(fruit: 'orange' | 'apple'){}fruitFn('apple');
While the following code will fail to compile:
function fruitFn(fruit: 'orange' | 'apple'){}fruitFn('banana');
Mixed Type
The mixed
type lets us assign any value to a variable or a function parameter. For example, we can write:
function foo(value: mixed) {
}
foo('apple');
foo(1);
foo(true);
With mixed
types, we have to figure out the actual type before doing manipulating the parameter or variable. We can use the typeof
or instanceof
operator for checking the type before operating on them.
For example, we can check if a value is a string or number as follows:
function foo(value: mixed) {
if (typeof value === 'number'){
return value;
}
else if (typeof value === 'string'){
return +value;
}
}
We can also use the instanceof
operator to check the constructor the object is created from and run code accordingly:
function foo(value: mixed) {
if (value instanceof Date){
return value.toUTCString();
}
else if (value instanceof Object){
return value.toString();
}
}
If Flow doesn’t know what type of object we’re passing in, then it won’t accept the code. For example, we can’t write:
function foo(value: mixed) {
return value.toString();
}
We’ll get the error:
[Flow] Cannot call `value.toString` because property `toString` is missing in mixed [1].
Any Type
The any
type let us opt out of type checking with Flow. It lets us assign it to anything if it’s a variable and pass in anything if it’s a function parameter.
It doesn’t check if the method we’re calling actually exists or the operator we’re using actually works with the data we’re passing in.
any
type isn’t the same as the mixed
type. mixed
type is checked for the type. We’ve to check the type with our code because we can do anything with the variable or parameter with the mixed
type.
Variables or parameters with the any
type do not have any type check, so Flow lets us do anything we want with variables of any
type. This means that we can get run-time errors like any other JavaScript code if we didn’t check the type of data is being manipulated.
When variables or parameters of type any
are assigned to other variables without type annotation. It’ll be set as the any
type.
We should stop this by annotating our variables or parameters with a type.
For example, we should write:
function fn(obj: any) {
let bar: number = obj.bar;
}
Then type checking will be done on bar
. This means that
function fn(obj: any) {
let bar: number = obj.bar + 1;
}
will work, but
function fn(obj: any) {
let bar: number = obj.bar + 'abc';
}
will fail.
Flow has many data types that make type annotation convenient coming from JavaScript. We can use the mixed
type to pass in anything or assign a variable to anything, but then we can check the type in our code after passing in the value or variable assignment.
any
type bypasses Flow’s type checking and lets us assign any value or pass any value to a function.
Literal types lets us set a variable or parameter to the possible values listed, separated by a |
symbol.
Maybe types make properties or variables be optionally passed in or stay undefined
.