Categories
Flow JavaScript

JavaScript Type Checking with Flow — Variables and Functions

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.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Utility Types

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 the built-in some utility types that come with Flow.

$Keys<T>

The $Keys<T> type gets the properties from a type and uses it as its own type. It’s also useful for creating enums.

For example, we can write:

const fruits = {  
  apple: 'Apple',  
  orange: 'Orange',  
  banana: 'Banana'  
};

type Fruit = $Keys<typeof fruits>;  
let a: Fruit = 'apple';

The code above will define a new type Fruit with the union of the keys of the fruits object. This means that with a variable of type Fruit , we can assign 'apple' , 'orange' or 'banana' to it.

Assigning anything else should get us an error:

let b: Fruit = 'grape';

$Values<T>

The $Values<T> type let us get the types of an object type and create a type from a union of them.

For example, we can write:

type Person = {  
  name: string,  
  age: number,  
};

type PersonValues = $Values<Person>;  
let a: PersonValues = 1;  
let b: PersonValues = 'Joe';

In the code, we defined the Person type, with some properties, and then $Values<Person> takes the types of name and age and create a union from it. So the type of PeronValues is string | number .

$ReadOnly<T>

$ReadOnly<T> gets us a read-only version of type T .

The following:

type ReadOnlyObj = {  
  +foo: string  
};

and:

type ReadOnlyObj = $ReadOnly<{  
  foo: string  
}>;

are equivalent.

This is handy when we want to redefine all the properties of an object type as being read-only.

For example, we can write the following to define a read-only type and object:

type Person  = {  
  name: string,  
  age: number,  
};

type ReadOnlyPerson = $ReadOnly<Person>;

let person: ReadOnlyPerson = {  
  name: 'Joe',  
  age: 10  
}

Then when we try to reassign a property in the person object as follows:

person.name = 'Bob';

We’ll get an error.

$Exact<T>

$Exact<T> let us create an exact type from an object type.

This means that:

type ExactPerson = $Exact<{name: string}>;

is the same as:

type ExactPerson = {| name: string |};

$Diff<A, B>

$Diff<A, B> creates a type with the properties that’s in A but not in B , where both of them are object types. It’s the same as A \ B , which is the set difference between A and B .

For example, if we have 2 types:

type Person  = {  
  name: string,  
  age: number,  
};

type Age = { age: number };

Then we can create an object type which accepts the name property with:

type Name = $Diff<Person, Age>;

When we create an object with the Name type, we require the name property:

let name: Name = { name: 'Mary' };

If the name property isn’t included in the object:

let foo: Name = { age: 10 };

Then we’ll get an error.

$Rest<A, B>

$Rest<A, B> is similar to $Diff<A, B> but the property check is done at run-time. It’s the properties that are part of the rest operator.

It results in the properties of A ‘s own properties that aren’t own properties of B . In Flow, exact object types are treated as having own properties.

For example, we can define a new object type with $Rest<A, B> by running:

type Person = { name: string, age: number };  
const person: Person = {name: 'Jon', age: 42};  
const {age, ...rest} = person;  
(name: $Rest<Person, {|age: number|}>);

Then the name object can’t have a property age anymore since we casted it to the $Rest<Person, {|age: number|}> type. $Rest<Person, {|age: number|}> returns a new type with the age property removed from the Person type.

$PropertyType<T, k>

$PropertyType<T, k> gets the type of key k from type T . Key k is a string. For example, if we create the Person type as follows:

type Person = { name: string, age: number };

Then $PropertyType<Person, ‘name’> will get us the string type:

let foo: $PropertyType<Person, 'name'> = 'name';

Nested lookups also work. For instance, we can write:

type Person = {   
  name: {  
    firstName: string,  
    lastName: string  
  },   
  age: number   
};let foo: <$PropertyType<$PropertyType<Person, 'name'>, 'firstName'> = 'name';

$ElementType<T, K>

$ElementType<T, K> is a type that represents every element in an array, tuple, or object type T that matches the key name K .

For example, if we have the following type:

type Person = {   
  name: string,   
  age: number   
};

Then we can use it to get the type for the name property by writing:

$ElementType<Person, 'name'>

And then we can use it to annotate the type of other variables:

let foo: $ElementType<Person, 'name'> = 'name';

We can use it with tuples as follows:

type Tuple = \[boolean, string\];  
let foo: $ElementType<Tuple, 0> = true;

With dynamic object types, we can pass in the type of the key directly to the second argument. For instance, if we have the following type:

type DynamicObj = { \[key: string\]: number };

Then we can get the type of the property keys with $ElementType by writing:

let x: $ElementType<DynamicObj, string> = 1;

For arrays, we can write the following:

type StrArrayObj = {  
  strings: Array<string>,  
};

let x: $ElementType<$ElementType<StrArrayObj, 'strings'>, number> = 'abc';

The code above works as follows. $ElementType<StrArrayObj, ‘strings’> gets us the type for the strings property of StrArrayObj . Then we apply $ElementType with $ElementType<StrArrayObj, ‘strings’> , and number to get the type of the array elements of strings .

The number is for retrieving the index of the strings array.

Flow has many useful utility types which make creating new types easier. We can get the keys of an object to create a new union type. Also, we can get the types of values and create a new union type from them.

Also, there’s a type to change all the properties to read-only. We can also get the types of object keys, tuples or array entries.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Union Types

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 use union types to create variables that accept several different types.

Union Type Syntax

The general syntax for defining a union type is as follows:

Type1 | Type2 | ... | TypeN

We separate each type with the vertical bar | .

Also, we can break them into multiple lines by using leading | :

type Bar =  
  | Type1  
  | Type2  
  | ...  
  | TypeN

We can create union types out of other union types:

type Numbers = 1 | 2;  
type Fruit = 'apple' | 'orange';type Foo = Numbers | Fruit;

Functions

If we use union types in functions, we have to handle every possible type of the union type of the parameter. For example, we have to write something like:

function foo(value: number | boolean | string):  number {   
  if (typeof value === 'number') {  
    return 1;  
  } else if (typeof value === 'boolean') {  
    return 2;  
  }  
  return Number(value);  
}

If we skip the handling of any type in the union type of the parameter like the following code:

function foo(value: number | boolean | string):  number {   
  if (typeof value === 'number') {  
    return 1;  
  } else if (typeof value === 'boolean') {  
    return 2;  
  }    
}

then we’ll get an error.

Unions & Refinements

With union type parameters, it’s useful to handle each type individually. We can do this with the typeof operator to check for the type and handle each type as follows:

function foo(value: number | boolean | string): number {   
  if (typeof value === 'number') {  
    return 1;  
  }   
  else if (typeof value === 'boolean') {  
    return 2;  
  }   
  else if (typeof value === 'string') {  
    return Number(value);  
  }  
  return Number(value);  
}

Disjoint Unions

We can create a union type out of different object types. For example, if we have the following objects:

{ foo: true, bar: 'bar' }  
{ foo: false, bar: 'baz' }

We can create a union type out of these 2 objects by creating a type alias for each and then union them. So we can write:

type Foo1 = { foo: true, bar: 'bar' };  
type Foo2 = { foo: false, bar: 'baz' };
type Foo = Foo1 | Foo2;

Then we can create variables of type Foo as follows:

let foo1: Foo = { foo: true, bar: 'bar' };  
let foo2: Foo = { foo: false, bar: 'baz' };

The union of 2 object types with fixed values will allow our variables to take on either of the 2 types in the union.

Union of object types with properties that aren’t exact will create a union of the 2 types for the properties.

For example, if we have:

type Foo1 = { foo: true, bar: string };  
type Foo2 = { foo: true, bar: number };  
type Foo = Foo1 | Foo2;

Then Foo will allow bar to be either a string or a number, while foo will always be true . So we can create variables like the following:

let foo1: Foo = { foo: true, bar: 'bar' };  
let foo2: Foo = { foo: true, bar: 2 };

If we have 2 types with different properties, then the union of 2 types let us include all the properties of the object if we have a variable with the union of the 2 types.

For example, if we have:

type Foo1 = { foo: true, bar: string };  
type Foo2 = { baz: false, a: number };
type Foo = Foo1 | Foo2;

Then we can define a variable with all the properties as follows:

let foo: Foo = { foo: true, bar: 'bar', baz: false, a: 1 };

With union types, we can also pass in extra properties that aren’t included in the types that form the union. So given:

type Foo1 = { foo: true, bar: string };  
type Foo2 = { baz: false, a: number };
type Foo = Foo1 | Foo2;

So we can write:

let foo: Foo = { foo: true, bar: 'bar', baz: false, a: 1, b: 2 };

Disjoint Union with Exact Types

We can restrict the addition of new properties by creating a union out of exact types as follows:

type Foo1 = {| foo: true, bar: string |};  
type Foo2 = {| baz: false, a: number |};
type Foo = Foo1 | Foo2;

Then writing:

let foo: Foo = { foo: true, bar: 'bar', baz: false, a: 1, b: 2 };

will give us an error.

With unions of exact types, we can either have properties from one or the other. So given these types:

type Foo1 = {| foo: true, bar: string |};  
type Foo2 = {| baz: false, a: number |};
type Foo = Foo1 | Foo2;

We can either have:

let foo: Foo = { foo: true, bar: 'bar' };

or:

let foo: Foo = {  baz: false, a: 1 };

With union types, we can create variables that can take on the value of several types. The types that form a union can primitive or object types.

We can also form unions of exact types to prevent adding additional properties that aren’t listed in the types or including properties from all the types.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Typeof Types

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 typeof types, which are types that are derived from other values.

The JavaScript Typeof Operator

In JavaScript, the typeof operator is mostly useful for checking the type of primitive values. For example, it works for numbers, booleans, and strings:

typeof 2 === 'number'  
typeof true === 'boolean'  
typeof 'abc' === 'string'

For objects, it always returns 'object' :

typeof { foo: 'abc' } === 'object'

Flow’s Typeof Operator

Flow’s typeof operator is much more useful. It can be used to return the value of any type.

For example, it can get the type for numbers as follows:

let num = 42;  
let num2: typeof num = 3.14;

It’ll fail when we assign values of any other type to it, like the following:

num2 = 'foo';

The benefit of Flow’s typeof operator is that it can also be used for objects. For instance, if we have the following object:

let obj = { foo: 1, bar: true };

Then we can use the typeof operator to assign the obj object as a type of another object as follows:

let obj2: typeof obj = { foo: 1, bar: false };

As long as we have all the properties of obj with the same data type, then it satisfies the requirement of the typeof obj type.

We can do the same for arrays as follows:

let foo = [1, 2, 3];  
let bar: typeof foo = [3, 2, 1];

typeof foo will infer that the type of foo is a number array, so we can assign another number array to bar .

Type Inference

The typeof operator does type inference implicitly. When we use literal values with Flow, then the inferred type is the primitive that the value belongs to.

For example, 1 would be a number . We can override the inferred type by explicitly asserting the type of variable we define as follows:

let num1: 1 = 1;

Then assigning num1 to anything other than 1 will fail:

let num2: typeof num1 = 3;

The code above will give us an error.

Typeof’s Nominal Type Check

Like other parts of Flow, the typeof operator also checks type nominally. This means that it checks the name rather than the structure. Therefore, even though 2 types have the same structure, but when their names are different, then they’re considered different.

For example, if we write:

class Foo {  
  foo(val: number) { }  
}

class Bar {  
  foo(val: number) {  }  
}

Then the following assignment would work:

let x: typeof Foo = Foo;

because both types have the same name, but:

let y: typeof Foo = Bar;

would fail even though Foo and Bar have the same structure since the have different names.

With Flow’s typeof operator, we can identify types of objects in addition to primitives. It’s consistent with Flow’s nominal type system in that it also checks the name by its name instead of the structure.

Also, the typeof operator can infer types from the data that’s assigned to a variable. For example, if a variable is a number array, then using the typrof operator on that array will return number array as the type.

Categories
Flow JavaScript

JavaScript Type Checking with Flow — Type Casting

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 cast values from one type to another.

Syntax

To value to a given type, we can use the following syntax:

(value: Type)

It can be used in variables, objects, or arrays. In variable assignments, it can be used as follows:

let foo = (value: Type);

With objects, we can cast the values of properties by writing:

let obj = { prop: (value: Type) };

Also, we can cast array entries by writing:

let arr = ([(value: Type), (value: Type)]: Array<Type>);

We can also cast expressions into a type. For example, we can write:

(1 + 1: number);

to cast numbers.

Type Assertions

We can only cast types when they make sense. For example, if we have:

let x = 1;

Then we can write:

(x: 1);

or:

(x: number);

since x is a number with value 1 . Casting to a type that isn’t the type of the value will fail. So casting the variable x to s string by writing:

(x: string)

will give an error since x is a number.

Type Casting

We can cast a value to a broader type than the original type of a variable. For example, if we have a number variable:

let x = 1;

Then we can either cast x to the 1 literal type or the number type as follows:

(x: 1);   
(x: number);

When we assign a variable to a new variable, we can also cast it to the same broad type:

let y = (x: number);

But casting it to a narrower type will fail, so if we write:

(y: 1);

we would get an error since y is expected to be a number with an indeterminate value.

Type Casting Through any

We can cast anything to any , which then let us cast it to anything else.

For example, if we want to cast a number into a string, we can see that we can’t do it directly from the examples above since we can’t cast anything to an unrelated type.

However, we can bypass that by first casting to any , then we can cast it to any type we want.

So we can write:

let x = 1;  
(x: 1);   
(x: number);  
let y = ((x: any): string);  
(y: string);

This isn’t recommended, but it may be convenient for some situations where we need to cast something from one type to an unrelated type.

Type Assertions

We can set the type of an object by casting the type of the object when we operate on it in a function. For example, if we have a function that clones an object, we can write:

function foo(obj) {  
  const cloneObj = {...(obj: { [key: string]: mixed })};  
  return cloneObj;  
}

Then when we call it, Flow is smart enough to determine the type of the properties:

let cloneObj = foo({  
  a: 1,  
  b: true,  
  c: 'three'  
})  
(cloneObj.a: 1);  
(cloneObj.b: true);  
(cloneObj.c: 'three');

This is more useful than putting the type in the parameter:

function foo(obj: { [key: string]: mixed }) {  
  const cloneObj = {...obj};  
  return cloneObj;  
}

Since Flow won’t know the structure of cloneObj this way.

We can also use generics with the $Shape keyword to assert the type of the obj parameter as follows:

function foo<T: { [key: string]: mixed }>(obj: T): $Shape<T> {  
  const cloneObj = {...obj};  
  return cloneObj;    
}

The code<T: { [key: string]: mixed }> will let Flow know that the obj parameter accepts an object.

With Flow, we can cast types to relevant types given the object’s value by using the : and the type identifier after it.

Also, we can cast something to an unrelated type by first casting it to any .

Finally, to make Flow be aware that a variable or parameter is a dynamic object, we can use the <T: { [key: string]: mixed }> and (obj: T) signature to make sure that the parameter is a dynamic object.

Then we set $Shape<T> as the return type to enforce that type of the object returned by the function is a dynamic object.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.