Log In

Flow introduction


by Yariv Katz

Flow is a static type checker for js code. A bit of history, in the end of 2014 microsoft released it's first version of a programming language called typescript that compiles to js. Typescript is a superset of js with additional static tools we can use. At the same year just a bit after typescript was first released, facebook released the first version of flow which allows us to add static type checking in our js code. The usage of those libraries grew while they were combined with js frontend libraries and frameworks. Angular 2+ was developed from start with typescript, React started pushing the usage of flow and combines flow with the create-react-app cli that starts a new react project. Those two technologies emphasise the importance of static type checking especially in larger projects. In this tutorial we will review flow and what we can do with flow type checker.

Installing flow

Create a new directory for your project. In this directory we will init npm and install flow. After installing flow we need to init it in the current directory. We can run flow to check our files with flow command.

> npm init --yes
> npm install flow-bin --save-dev
> npx flow init
> npx flow

When we init flow, the result is a file called .flowconfig This configuration file will instruct flow how to do it's job. It will not be so effective if we would have to run the flow command every time we want our code to be checked. What we do want is the type checker to work with our IDE, alerting us while we type if we have errors in our code. Let's try to combine flow with Visual Studio Code.

Flow and VS Code

Working with flow will be much more effective if we could combine it with our IDE. Open VS Code. On the Left toolbar the bottom most icon is the Extension section. goto the extension section. In here we will have to install the flow extension, and also disable the typescript built in extension. In the extension search and install an extension called Flow Language Support. To disable typescript for this project click the 3 horizontal dots in the extension panel and in the popup menu choose: show built in extensions. Search for @builtin typescript For the list click on TypeScript and JavaScript Language Features, and choose to disable this extension for the current workspace. You will have to Reload the IDE to reactivate the changes you made. Now when you type js code in your IDE it will be flow checked for errors.

Babel

JS files with flow syntax will not be understood by our browsers. We will need to transpile our code and remove the flow syntax in our final js files that will be run by the browsers. Similar to typescript where the files need to be compiled, the same is with flow the files need to be transpiled to js understood by the browser. To do this we need to install Babel transpiler and install it with the proper presets to transpile flow. Note that if you are creating a project using create-react-app cli, then installing flow and babel and configuring them is already done for you. In the terminal type:

> npm install --save-dev babel-cli babel-preset-flow

we also installed a preset of flow, which is sort of a way to instruct babel how to process the js files. We want to instruct babel to use our preset so we will have to configure babel. Configuring babel is done via a file called .babelrc Create the file .babelrc with the following:

{
    "presets": ["flow"]
}

Lets place all of our js flow code in a folder called src. To compile with babel all our code in the src folder:

> mkdir src
> npx babel src -d dist

to create a shortcut for this command we can add the command in the scripts section of our package.json. Modify package.json

{
    "name": "flow-tutorial",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "build": "babel src -d dist"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "babel-cli": "^6.26.0",
        "babel-preset-flow": "^6.23.0",
        "flow-bin": "^0.86.0"
    }
}

And now to run babel and transpile our flow files we can simply:

> npm run build

Files

Unlike typescript, flow will not process all the files in the src directory. It will process all the files that has the commend: //@flow files without this comment will be ignored, and not checked.

Basic types

Let's examine flow types. To specify that a variable is of type the general syntax is:

var/let/const variableName: Type

In your project create a file called types.js where we will experiment with the different flow types. Let's start with the primitives:

// @flow

// Primitives

const myNumber: number = 10;

let myBoolean: boolean = true;
// myBoolean = 10; // Error!

var myStr: string = 'hello world';

let myUndefined: void = undefined;
// myUndefined = null; // Error!

var myNull: null = null;
// myNull = undefined; // Error!

Flow will verify that the types are being maintained and we are not pushing a string value to a variable of number type. Even if we do not specify the type flow can guess the type by the assignments for example:

let noType = 'hello world';
noType = 10;

myStr = noType;

Even though noType started as a string, we assigned a number to it and flow knows that the variable is of type number now, so trying to place that variable to string will bring an error. If the variable is not of type null or void it cannot contain null or undefined. For example this will yield error:

myBoolean = undefined; // Error!
myBoolean = null; // Error!

Instead you can define a maybe type where the type can be either the type you want or null and undefined. For example this is an example of a boolean that can get a null and undefined as well:

let myBoolean2: ?boolean = true;
myBoolean2 = null;
myBoolean2 = undefined;

With union types you can specify the a variable can contain union of multiple types. For example if we want to define a variable that can hold either a number of a boolean:

let numOrBool: number | boolean;
numOrBool = true;
numOrBool = 10;
// numOrBool = 'asdf'; // Error!

you can define a variable as mixed and that variable can contain any type. There will still be a check on that type so we will to refine the time when doing operations on the type. For example:

let myMixed: mixed = 'hello';
// myMixed.length; // error!
if (typeof myMixed === 'string') {
    console.log(myMixed.length);
}

refinement is the processof limiting the type of the variable. Flow can understand the if we placed and can understand that if we entered the if the type of the variable is string and it has a length property. otherwise refering to the length property will present an error. If you want to define a variable that can contain anything but flow will not present error on that variable, you can define a variable of type any.

Complex types

We can use flow to define more complex types. Let's create a new file called complex.js. Let's define variables of array types:

// @flow

const myNumArray: Array<number> = [];
const myBoolArray: boolean[] = [true, false];

myNumArray.push(1);
// myNumArray.push('hello'); // Error!

Flow will maintain that our array contains the proper types. We can also define an object:

const myStrObj: {[string]: string} = {}
// myStrObj[1] = 'dfgs' // Error!
myStrObj['hello'] = 'world';

Functions

Let's experiment with the tools flow gives us regarding functions. Create a new file called functions.js. We can define a variable that can get a function like so:

// @flow

let myFunc: (number, string, boolean) => string;

// myFunc = (str: string, num: number) => str; // Error!
myFunc = (num: number, str: string, bool: boolean) => str;

We can specify the types the functions get as arguments as well as what the function will return.

function stam(str: string = 'hello', num?: number): number {
    return str.length;
}

// stam([]); // Error!
stam('hello');

you can also specify an optional argument and a default value to arguments.

Class

When defining ES6 classes you can also use them as types. There will be type checking on the class properties so you will have to define the properties before assigning to them. You can also specify the class methods return type and argument types. Create a new file to experiment with the classes called classes.js

// @flow

class Person{
    age: number;
    
    constructor(age) {
        this.age = age;
    }

    sayHello(): string {
        return `hi my age is: ${this.age}`
    }
}

const me: Person = new Person(33);

Type Aliases

We can use the word type and after that define a type we can reuse. This is commonly used in React components where we can define the type of the Props and State the component will get. Create a file called aliasing.js with the following:

// @flow

type Props = {
    foo: string,
    isLoading: boolean
}

const item: Props = {
    bar: 'dfgs',
    foo: 'adfsg',
    isLoading: true
}

interface

You can define an interface that classes can implement. For example you can define an interface for a class with a property and a method and the class will have to implement that. Create another file called interfaces.js with the following:

// @flow

interface IPerson {
    age: number,
    +sayHello: () => string;
}

class Person implements IPerson {
    age: number;
    constructor(age) {
        this.age = age;
    }

    sayHello() {
        return 'hello';
    }
}

The + sign is meant to say that the property will have to be read only, - sign is write only.

Generic Types

You probably noticed in react that when we are creating components we can specify the props and state of the component as a type in <Props, State> This is refered to as generic type, and is used when the type is not known on definition and will be known on runtime, but we still want to use that type. You can attach generic types to class, interfaces, functions, type alias. Lets create a file called generics.js and place the following generic type in a class:

// @flow

class Student<T: string | number> {
    grade: T;
    
    constructor(grade: T) {
        this.grade = grade;
    }

    whatIsMyGrade(): string {
        return `my grade is: ${this.grade}`
    }
}

const me: Student<string> = new Student('C-');

Summary

We saw an overview of Flow main common features. Comparing to typescript I would have to say that typescript has a bit more strength like abstract classes, enums. On the other side it is harder to use typescript when using libraries that do not support typescript, and it is also hard to combine when we want to keep part of our project as js. So using typescript kind of makes us switch all of the project to typescript. Typescript however has a much better auto completion on IDE's Flow is easier to combine on existing js projects and it is much easier to work on React projects with flow cause typescript is not working so well with react libraries last time I checked. The resemblence between flow and typescript makes it easier to switch between those two technologies. And it is starting to be more obvious that on large projects with large teams, it's much easier to add staticness to your code so flow or typescript are a must.