DEV Community

John Au-Yeung
John Au-Yeung

Posted on

JavaScript Type Checking with Flow — Variables and Functions

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/

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 data types supported by Flow for type checking, including variables and basic properties of functions.

Variable Types

Just like in JavaScript, we can declare variables and constants with Flow with the let, var, and const keywords respectively.

let and var are used to declare variables. This means that their values can be reassigned after the initial assignment.

const

The const keyword is for declaring constants. Once it’s assigned a value, it can have another value reassigned to it.

Flow can infer the data type constants without adding type annotation:

const a = 1;

It’ll know that a is a number.

Of course, we can annotate it with a type as follows:

const a: number = 1;

var and let

var and let are both for assigning to variables. The difference is that var isn’t block scoped while let is.

For example, we can write:

if (true){  
  var x = 1;  
}  
console.log(x);

However, if we replace var with let , we’ll get an error:

if (true){  
  let x = 1;  
}  
console.log(x);

We can reassign variables declared with var or let with another value. For example, we can write:

var x = 1;  
x = 2;
let y = 1;  
y = 2;

Of course, if a data type annotation is added to a variable, then we must have a value with a compatible type assigned to it. For example, if we have a number variable:

let y: number = 1;

Then writing:

y = '2';

after it will create an error.

If a data type annotation isn’t added to a variable or constant, Flow will assume that the type of the variable or constant is the union of all the types of the data that had been assigned to it.

For example, if we have:

let a = 1;  
a = 'foo';  
a = true

Then we can write:

let b: number|string|boolean = a;

without getting any errors. This is because we assigned a a number, string and boolean before we assigned a to b , so a is assumed to have a union of all those types.

Flow can also figure out the type given that it’s been assigned. For example, we can write:

let foo = 1;
let num: number = foo;
foo = false;
let boo: boolean = foo;
foo = "foo";
let str: string = foo;

However, code that’s run within blocks like functions and conditions will throw off Flow’s type inference and will throw an error when we try to assign it to something that we expect to work.

For example, if we write:

let foo = 1;
if(true) {
  foo = true;
  foo = "foo";
}
let str: string = foo;

We’ll get the error:

[Flow] Cannot assign `foo` to `str` because number [1] is incompatible with string [2].

since Flow doesn’t know the type of the foo variable.

Likewise, Flow can’t figure out what type foo has after it’s been reassigned values inside a function as follows:

let foo = 1;
const fn = ()=> {
  foo = true;
  foo = "foo";
}
fn();
let str: string = foo;

Function Types

A function can have type annotations for its parameters and the return type of the function.

For example, if we have the function:

function add(a: number, b: number): number {  
  return a + b;  
}

Then the number in a: number and b: number are the parameters’ data types, and the number after the close parentheses is the return type of the function add .

Once we annotate the parameters with types, Flow will validate the types when anything is passed in.

For example, if we have:

add(1, 2);

Then Flow will accept the code as valid since we passed in numbers as indicated in the function definition.

On the other hand, if we pass in data of any other type like string as follows:

add('a', 'b');

Then we get the error:

[Flow] Cannot call `add` with `'b'` bound to `b` because string [1] is incompatible with number [2].

Since we passed in strings for the arguments, which aren’t compatible with numbers.

Function Syntax

Flow’s function syntax is similar to JavaScript but added extra type annotations to the parameters and return type.

We can have an infinite number of parameters with the rest operator, which is indicated with the ... operator and holds the arguments that are excess of the listed parameters as an array.

For example, we can write:

function foo(str: string, bool?: boolean, ...nums: Array<number>): void {
  console.log(nums);
}
foo('a', false, 1, 2, 3);

Then we get [1,2,3] for nums .

However, if we pass in something invalid as follows:

foo('a', false, true, 2, 3);

Then we get an error for passing in true into the third argument since we specified that ...nums is a number array.

We can declare variables and constants like JavaScript with Flow. In addition to JavaScript’s syntax, we can add data type annotations to variables and constants.

Function syntax and definition also extends from JavaScript’s syntax. Like variables, we can add data type annotations to variables, including ones operated on by the rest operator, and also add a return type annotation to a function.

Top comments (2)

Collapse
 
diek profile image
diek

This is interesting, does flow transpile too? It's the only thing I don't like about typescript, that sometimes it does some weird code transformation. I will search more about this. Thank you!

Collapse
 
aumayeung profile image
John Au-Yeung

Yes. It's supported by Babel and Create React App.

You can also incorporate Flow code as JavaScript comments, which means no transpiling required.