Subscribe to my email list now at http://jauyeung.net/subscribe/
Follow me on Twitter at https://twitter.com/AuMayeung
Many more articles at https://medium.com/@hohanga
Even more articles at http://thewebdev.info/
Since JavaScript is a dynamically typed language, there’re going to be problems with variables and object properties having unexpected types. Also, some JavaScript type coercion rules are arbitrary. To make developing JavaScript apps easier, Facebook made extensions to JavaScript to add type annotations and type checking to the language.
Adding Flow to a Project
The language, which consists of Facebook’s extension to JavaScript along with the original JavaScript language, is called Flow.
We can easily use it in a new project. To add it globally, we just install Yarn and run:
yarn add --dev @babel/core @babel/cli @babel/preset-flow
Then, we create a .babelrc
file and add the following:
{
"presets": ["@babel/preset-flow"]
}
Then we can build and run the code:
yarn run babel src -- -d lib/
The command can be added to the package.json
file as follows:
{
"name": "project-name",
"main": "lib/index.js",
"scripts": {
"build": "babel src/ -d lib/"
}
}
We can also add it per project by running yarn add --dev flow-bin
in our project, adding Flow as a development dependency for the project. Then we can run the flow
command: yarn run flow
(You may have to run yarn run flow init
if you haven’t run it before).
Adding Flow Code to for Type Checking
Now that Flow is installed, we can add type annotations to our code to let Flow do type checking.
The simplest way to do this is to add type annotations after a variable to indicate its type:
// @flow
let x: number = 1;
Notice that we have to add // @flow
over our code to enable Flow to do the type checking of our code.
Also, we can add type annotations to function parameters:
// @flow
function cube(x: number): number {
return x**3;
}
Then when we write: cube('');
we get the following error:
[Flow] Cannot call `cube` with empty string bound to `x` because string [1] is incompatible with number [2].
This is because we passed in a string to a function that expects a number.
If we pass in a number, it should run:
cube(2);
Basic Types
Flow has its own type annotations for various data types that we’ll encounter in JavaScript.
Like JavaScript, it has various primitive data types. JavaScript has the following primitive types:
- Booleans
- Strings
- Numbers
-
null
-
undefined
(void
in Flow types) - Symbols (since ES6, but not yet supported in Flow)
They’re available as literals or constructed with wrapper constructor or functions:
//literals
true;
"abc";
1;
null;
undefined;
// constructors
new Boolean(false);
new String("abc");
new Number(1);
// factory function
Symbol("abc");
Literal values have type names in lower case. Objects of types that are created with constructors have type names in upper case. For example, we can have:
// literals
x: number
// constructor objects
x: Number
Booleans
Booleans can be either true
or false
. We can write:
// @flow
function boo(val: boolean) {
}
boo(true);
boo(2);
boo(2);
will give us the follwing error:
[Flow] Cannot call `boo` with `2` bound to `val` because number [1] is incompatible with boolean [2].
JavaScript converts objects to boolean when it’s needed. For example, it will convert it inside if
statements or equality statements when we compare to booleans.
In JavaScript, we can write this:
if (1) {}
This is because it has truthy and falsy values. Falsy values are converted to false
when conversion to boolean is done. The following values are falsy:
- 0
-
undefined
-
null
-
NaN
- empty string
-
false
Everything else is truthy.
With Flow, we have to explicitly pass in boolean values if we have a parameter or variable of type boolean
or Boolean
. boolean
and Boolean
types aren’t interchangeable with Flow. Given that we have:
// @flow
function boo(val: boolean) {
}
If we write boo(new Boolean(true));
we’ll get the following error:
Cannot call `boo` with `new Boolean(...)` bound to `val` because `Boolean` [1] is incompatible with boolean [2].
Numbers
JavaScript has only one type of number. It can be floating-point or an integer.
Flow has both the number
and Number
types. The number
type is for numeric literals and the Number
type is the wrapper object.
For example, we can write:
let x: number = 1;
Or we can write:
let x: Number = new Number(1);
However, we can’t write:
let x: number = new Number(1);
The code above will get us the following error:
[Flow] Cannot assign `new Number(...)` to `x` because `Number` [1] is incompatible with number [2].
We also can’t write:
let x: Number = 1;
This will give us the following error:
[Flow] Cannot assign `1` to `x` because number [1] is incompatible with `Number` [2].
Strings
Flow has the string
and String
types for the literal and wrapper object respectively.
It works like other primitive types. So we can write:
let x: string = 'abc';
Or we can write:
let x: String = new String('abc');
But we can’t flip the literal and wrapper object assignments.
Flow will only accept concatenation of strings and other strings or numbers. This means the following will work:
"foo" + "bar";
"foo" + 1;
We can also concatenate a string literal with a wrapper object string. For example, we can write:
"foo" + String('abc');
However, we can’t concatenate a string with a String created with the String constructor. So the following will give us an error:
"foo" + new String('abc');
Concatenating string with any other types will get us errors.
null
and void
Flow has the null
type to represent the null
value, and void
to represent the undefined
value.
So the following will work:
let x: null = null;
let y: void = undefined;
Nothing else can be assigned to these types. This is the same for function parameters.
Facebook created Flow to let us check the types of data in our code for correctness. It prevents unexpected type assignments to variables and function parameters.
For primitive types, it has separate types for objects created with constructors for primitive objects and literal. For example, string
is for the string literal and String
is for strings created with new String(...)
.
It also has the null
and void
types for null
and undefined
respectively.