Notes on Flow & Static Type Checking for JavaScript

For Intermediate to Advanced JavaScript Developers

These notes discuss using the Flow type checker that can be bolted to JavaScript forcing it to act like a static programming language during development.

1 : What is a JavaScript Type?

This section briefly describes types in the context of JavaScript and through the lense of Flow.

1.1 - JavaScript is Dynamically Typed

The JavaScript language is made up of data structures like Boolean, Null, Undefined, String, Object, etc... . When creating a named data structure (e.g. a variable, a function parameter, an object property) in JavaScript the language does not require that you define/annotate what type(s) of data structure will be reference/stored. In JavaScript you can create a parameter, variable, or object property and assign it any data structure you want and then re-assign it with a new data structure type in the future without an error resulting from the change in data structure.


let myVariable = 1;    
// myVariable now references a value that is a number type data structure
// I did not tell JavaScript that myVariable would hold a number

console.log(typeof myVariable) // logs "number"

// JS dynamically, at runtime, figured out that the structure was a number and of the type number

myVariable = 'string';
// now, myVariable references a value that is a string type data structure

console.log(typeof myVariable) // logs "string"

myVariable = {};
// now, myVariable references a value that is an object type data structure

console.log(typeof myVariable) // logs "Object"

// The JavaScript runtime figures out data structure types dynamically the developer does 
// not tell the program what types before running the program

Keep In Mind:

  1. The change from one JavaScript data type to another does not always cause a JavaScript runtime error. The runtime will try and fix any type issues if it can. However using the wrong type of data in the wrong way will cause a runtime error e.g. using a string where only a number is expected, "foo".toFixed(); // throws error.

Because JavaScript allows type changes at runtime (e.g., when running in a browser) it is considered a dynamically typed language.

Note:

  1. Dynamically typed languages stand in opposition to statically typed languages because static programming languages typically involve a compiliation step that fails if type invariants occur (i.e. a type changes or is used incorrectly). While dynamically typed languages that are not compiled only show type errors at runtime. And, if it is weakly typed it will to some degree try and avoid errors by doing hidden type conversions to avoid runtime errors.
  2. JavaScript is weakly typed in the sense that it will do implicit (i.e. hidden) value conversions (i.e. coercive) at runtime trying to avoid errors using a complex set of rules for conversions (e.g. '3' * '2' === 6 // no error due to coercion). As languages do less and less conversions/coercion they become more strongly typed but yet can still be dynamically typed languages (e.g. Ruby, Python, Lua, PHP etc...)

1.2 - Making JavaScript Statically Typed (via Flow)

Some developers believe that statically typed languages that have a compiling/transpiler step, which do type checking before runtime execution, are superior to dynamically typed languages that have no compiling/transpiler step. The reason being that type errors can be surfaced before running the code.

One should think of Flow as a tool that forces invariants by analysis (i.e. invariants means the program fails unless all values have a type, inferred or explicitly defined, and that that type does not change and is used correctly at all times). Flow layers itself over the top of JavaScript introducing a static analysis step where it attempts to treat JavaScript as a static programming language. Flow analyzes static code, takes hints from Flow annotations, and tells you when your code does not look like a static programming language.

Keep In Mind:

  1. To layer a type system over the top of JavaScript requires adding a transpiler step to your development workflow to remove the type annotations from the source code (i.e. types have to be removed for JavaScript to actually run). If you are using Babel then you have already accept this step and Flow can be used to remove Flow syntax from your source code as it is being built for production by Babel.

To initially grok Flow and static types in the context of JavaScript consider that by using Flow one can define/annotate (e.g. :string and :number) what type of value a variable or parameter can store/reference. Provide the wrong type of data in the wrong place and Flow will complain/error.


// @flow 

let score:number;

score = '3'; // Error, the value must be a number not a string containing a number

function square(n: number) {
  return n * n;
}

square("2"); // Error, n can't be a string, just a number!

/* Flow command line tool or Flow running in your IDE will indicate the following errors:

3: score = '3'; // Error, the value must be a number
           ^ Cannot assign `'3'` to `score` because string [1] is incompatible with number [2].
References:
3: score = '3'; // Error, the value must be a number
           ^ [1]
1: let score:number;
             ^ [2]
9: square("2"); // Error, n can't be a string, just a number!
          ^ Cannot call `square` with `"2"` bound to `n` because string [1] is incompatible with number [2].
References:
9: square("2"); // Error, n can't be a string, just a number!
          ^ [1]
5: function square(n: number) {
                      ^ [2]
*/

Note that most statically type languages and static type systems like Flow will also infer types from expressions without annotations.


// @flow  

/* Flow is inferring that n in the function below should be a number not a string 
because it is pulling JavaScript from a weakly typed language to a strongly typed 
language trying to get it to error when ambiguity is introduced because of weakness 
in types or native coercion routines/rules. */

function square(n) {
  return n * n; // type error inferred because expression expects numbers not strings
  // Non-Flow JS would not error here, you'd get 4 
  // because native JavaScript try and do the correct thing
}

square("2");

/* Flow command line tool or Flow running in your IDE will indicate the following errors:

4:   return n * n; // type error inferred because expression expects numbers not strings
            ^ Cannot perform arithmetic operation because string [1] is not a number.
References:
7: square("2");
          ^ [1]
4:   return n * n; // type error inferred because expression expects numbers not strings
                ^ Cannot perform arithmetic operation because string [1] is not a number.
References:
7: square("2");
          ^ [1]
*/

Additionally, a type checker like Flow is not only concerned with implicit type annotations for variables, parameters, and object properties but also values returned from functions.


// @flow

function square(n: number):string {
  return n * n; // Error, this returns a number not a string
}

square(2);

/* Flow command line tool or Flow running in your IDE will indicate the following errors:

4:   return n * n; // Error, this returns a number not a string
            ^ Cannot return `n * n` because number [1] is incompatible with string [2].
References:
4:   return n * n; // Error, this returns a number not a string
            ^ [1]
3: function square(n: number):string {
                              ^ [2]
*/
    

2 : What exactly is Flow & do I need it?

This will briefly explain the concept of Flow and the need for it.

2.1 - What is Flow?

Flow is an open source JavaScript library written by Facebook that statically analysis your JavaScript files as if it was a static language instead of a dynamic langauge. It does this by inferring types and analyzing type annotations developers write over the top of JavaScript (e.g. let score: number, score can only be assigned values that are JavaScript numbers. But this isn't valid JS so the : number has to be removed before a JS engine will run).

Basically, it is a tool not unlike eslint that statically analyzes your code during development for invariant data type problems. It might be best to simply think of it as a linting tool that expects JavaScript syntax to conform to a typed system and when id does not, it errors/warns/blocks development, etc... .

2.2 - Do I need Flow?

No matter what anyone exposes about code quality, managing types does not mean you have to use static types and avoid dynamic types to have a maintainable code base. Flow usage is a reflection upon the subjective development whims of developers and their context (i.e. Not everyone has Facebook problems. Only facebook and their developers might have the problems that Flow fixes). It is likely the case that if you or your team requires the use of strict/strongly typed code, a bolted on type system will bring more problems than solutions. If types are an absolute requirement for your project consider using a natively type language (e.g. Reason, PureScript, Elm) before settling for Flow or TypeScript.

Keep In Mind:

  1. TypeScript, an alternative to Flow, is basically Flow + Babel in terms of static v.s. dynamically typed features. So Flow + Babel are not different from TypeScript in the context of type systems. Both at the end of the day bolt on a new non-native language features (i.e. a type system plus more e.g. Flow linting and much more in the case of TypeScript).

With that said, many individuals and teams find Flow (i.e. bolted on type systems) extremely helpful. But this is more telling of the individual or team and the problems and tradeoffs they wish to have and not exactly based on the objective merits of Flow (i.e. bolted on type systems) itself. Flows value is in the eye of the beholder. If you are trying to figure out if Flow should be used on a team, first asked if everyone on the team agrees Flow is absolutely required given its costs (know the costs!). Be concerned about co-workers who take a neutral, naive, or negative perspective on Flow because the effort to make it valuable takes 100% commitment from everyone who writes code where Flow is used. Flow usage is the kind of choice that requires 100% participation/buy-in from all team members for its value to rise above its costs. If the team can't reach a massive majority agreement on the usage of Flow then Flow should likely be avoided because the subjective/contextual value is directly tied to the commitment level and to the quality of its implementation.

Keep in Mind:

  1. For Flow to reach a tipping point in value I think it has to be implemented on day one and the implementation has to be normalized, documented, and syntax standards/rules have to be followed consistently across all modules (massive developer buy-in and conformance is needed). If you adapt it after the fact or assume developers will just figure it out in terms of conventions as they go, you'll likely never get the value out of the cost (the syntax variations cause massive indirection in the code). Adding Flow can result in a massively complex syntax and cruft coating all of your code.

2.3 - Flow Cons

  • In a lot of cases adding annotations to modules can double the line size of a module making them unnecessarily complex to read or change.
  • Flow comes with an entire layer of management around type modules (both custom and third-party type modules).
  • Type annotations and declarations (i.e. Flow syntax) are custom cognitive overhead that clutters your source code with verbose non-native syntax CRUFT that as of today will never become native to the language.
  • Typing out annotations and appeasing the Flow checker can be a massive time suck with little return to developers and overall code quality (Flow being the cause of removing a lot of important errors is a debated topic with no clear outcome).
  • JavaScript developers who have never used Flow will experience a significant learning curve to work within a new code base. Which is not ideal, when already trying to learning a new code base. Thus, making the code base even more difficult to work in and can result in hiring and development difficulties.
  • Your JavaScript source code is not valid JavaScript ( Flow comment syntax is available but not really used in practice).
  • Flow fails at simplistically, and with little effort, at turning JavaScript into a statically typed language. The outcome for large complex code bases, after a couple of years in the wild, seems to be use a language where it is built in (i.e. Reason, Elm, PureScript) because the overhead is not worth the return.
  • Flow is significantly a more complex type system and junk drawer of syntax than the one that comes with JavaScript. JavaScript already has a type system and one can learn it and use it in similar ways to manage types natively (KISS does more for maintainability and quality than endless configuration handcuffs and CRUFT).
  • Flow solves unrealized problems that you might not ever have instead of actual problems you clearly have today. And if you do have some degree of the problems that it claims to solve, the problems are trivial compared to the cost of implementing and maintaining Flow.
  • The case for type errors at runtime appears to be massively overstated. Does it happen yes. Is it manageable without a bolted on type system. Yes.
  • Flow error messages can be jarring and cryptic when compared to error messages from languages where static types are baked into the language.
  • The momentum/community around a type checker that is bolted on to JavaScript is not behind Flow it is firmly behind TypeScript. But both suffer from, historically, a losing proposition (i.e. bolting non-native languages changes to JS e.g. module systems)
  • Type definitions, or lack of, for Third party libraries and tools can force the hand of a developer to make sacrifices in code quality.
  • Flow has no powers at runtime.
  • What to type and how to type values can become a very subjective and debated topic among teams members which requires management and standards (i.e. overhead that can slow you down more than the bugs that might occur without Flow or worse slowness because no management or standards are created and enforced at all, reeking pain and suffering for anyone who has to commit code).
  • In theory, the value of Flow is wrapped up in this idea (type theory) that if all values are identified with a type(s) then one can prove how sound a program will be (not unlike logical proofs). The theory is good, however the implementations details wears at the theoretical. And the implementation is not simply convention alone (i.e. a hammer can be designed poorly regardless of if you use it correctly). The value of an unsound, bolted on, type system is not a given value. It is a debated topic with a long history of plus's and minus's hinging on context and implementation details.
  • Learning Flow to be an expert can take 6 to 8 months (likely less) and this is if the developer views it as worth learning well. If the developer views it as an annoyance and a massive time suck with little to no return, learning it might go on indefinitely. Either way, if developers have never seen your code or used Flow you've added an entire sub-language/syntax to be learned before learning about the actual code. And the worse part is this is not an isolated syntax but one that muddies the waters of JavaScript syntax itself (a drop of poison in a glass of water). Meaning, if you are use to JavaScript syntax alone having the mental overhead of a bolted on syntax can take a long time to consciously and unconsciously jive with all ones previous mental models of the language. Flow alters/evolves JavaScript syntax and this wears on the native lense through which many JavaScript developers have view JavaScript for years.
  • Not uncommon that Flow implementations (and it could be the implementation not Flow) turn into valueless overhead cycles that will slow production and give only a return of intangible and theoretical benefits. (i.e. developers prolong each code commit because they have to "rewrite their code to satisfy (or work around) the type system.")
  • Unless one blocks production code on Flow errors most developers will ignore Flow because appeasing Flow can be a confusing and time consuming routine.

2.4 - Flow Pros

  • Values (e.g. variables, parameters, object properties, expression outcomes, etc...) have a more explicit and obvious type. Which some claim can lead to fewer runtime errors and fewer regressions when refactoring.
  • Avoid cluttering unit tests with type checking routines.
  • Avoid writing type related checks in function calls and the corresponding error throwing boilerplate if the type checks fail. Many argue that if you don't have to write a lot of defensive code dealing with types of value you not only simplify your code but make it more preferment.
  • Possible that some JavaScript type coercion, resulting in runtime errors, can be avoided before runtime (i.e. earlier errors at compile time instead of run time).
  • Some degree of inline documentation for functions and classes is provided by using Flow syntax (e.g. what parameter is expect to a function, and in what form, and what value should be returned).
  • Can hint/inform when a refactor is incomplete or causes further regressions.
  • Improvement to developer experience when using certain IDE's (autocomplete, inline documentation etc...)

3 : Overview of thinking in Flow?

This will briefly discuss how Flow fits into most development routines.

3.1 - Developing with Flow

Flow is a node command line tool that can analysis your JavaScript files for types in near real time. Most developers have Flow integrated into their IDE so that as they work in their code Flow is always checking and reporting on Flow errors in real time without having to monitor or use a command line interface (i.e. using a .flowconfig file).

The files that Flow checks can be designated file by file or one can configure Flow to check all files of X type in certain directories. Given that you can tell Flow exactly which files to check and which files not to check many consider Flow to be something you can gradually adopt.

To designate the files you want Flow to monitor (assuming you have installed and are running Flow), from within the files themselves, simply add the following comment to the top of the file:

// @flow



or

/* @flow */



3.2 - Flow Type Inferences

Without adding any Flow type annotation syntax, and assuming a file has // @flow on the first line, Flow will infer types for all values in your file (Including imported values).


// @flow  

function square(n) {
  return n * n; // type error inferred because expression expects numbers not strings
}

square("2");

/* Flow command line tool or Flow running in your IDE will indicate the following errors:

4:   return n * n; // type error inferred because expression expects numbers not strings
            ^ Cannot perform arithmetic operation because string [1] is not a number.
References:
7: square("2");
          ^ [1]
4:   return n * n; // type error inferred because expression expects numbers not strings
                ^ Cannot perform arithmetic operation because string [1] is not a number.
References:
7: square("2");
          ^ [1]
*/
  

Note that without Flow annotations (i.e. inference alone), Flow will have too:

"If Flow is unable to figure out what the exact type is for each value, Flow must figure out what every possible value is and check to make sure that the code around it will still work with all of the possible types." - Flow Docs

Inference errors alone do not bring much value. The point of adding Flow to your code is so that you can annotate values with types (especially values like functions parameters, values returned from functions, Object property's, and Array values). Inferences from Flow is just a courtesy so you don't have to type everything, but Flow assumes that you are not only using Flow for inferences. Flow's value is tied up in using the annotating system it provides to explicitly track values (i.e. by using type annotations one can in a sense create logical proofs/formulas for all values so that it will be near impossible for type errors to occur at runtime.).

3.3 - What is a Flow Type Annotation?

Flow provides a syntax, pretty much an evolution of the JavaScript language, that it unfortunately calls "Types" (This can get confusing because it does not correlate exactly to native types in JavaScript). Flow type syntax/annotations are added to your JavaScript, which is used by Flow to statically lint/analysis your code in the context of a static type system.

Flow offers the following types (i.e. really an entire type syntax, including tools and utilities, for annotating native and user defined data values in JavaScript):

Primitive Types
Literal Types
Mixed Types
Any Types
Maybe Types
Function Types
Object Types
Array Types
Tuple Types
Class Types
Type Aliases
Opaque Type Aliases
Interface Types
Generic Types
Union Types
Intersection Types
Type Casting Expressions
Utility Types
Module Types

The remainder of these notes will detail these types

3.4 - Flow Language Restrictions/Linting

As previously mentioned, Flow can infer types and can be told about types via annotations. But Flow can also restrict the language via an internal Flow linting system (i.e. restrict how you write JavaScript itself). For example it will lint/complain about passing unexpected arguments to a function.


// @flow  

const myFunc = (x) => {
  return x;
}

myFunc(1,2);

/* Flow command line tool or Flow running in your IDE will indicate the following errors:

5: myFunc(1,2);
            ^ Cannot call `myFunc` because no more than 1 argument is expected by function [1].
References:
1: const myFunc = (x) => {                  ^ [1]

*/

These types of errors slightly extend Flow past a type checking system and into the realm of JavaScript conventions/linting restrictions.

3.5 - What can be Annotated?

Any value or expression in JavaScript can be annotated. If you're new to Flow or static types this might not help you grok exactly how JavaScript is annotated with Flow types. It might initially be helpful to think about what can be annotated (or inferred) in terms of:

Variable's:
Function Parameter's:
A Function's Return Value:
Object's
Array's
Class's
Expressions

Below each of the above typing situations are simplistically and briefly explored in order to initially reveal some insight into what can be typed (most of the examples use very simple Flow type annotations e.g. primitive types :string and :number):

Variable's:


// @flow

// Tell Flow that this variable can only hold a 
// string or undefined using primitive type annotations

let myVariable: string | void = undefined;

myVariable = 'string';

Function Parameter's:


// @flow

// Tell Flow that parameter a and b can only hold a 
// string using primitive type annotations

function concat(a: string, b: string){
  return a + b;
}

A Function's Return Value:


// @flow

// Tell Flow that this function can only return a
// string using primitive type annotations

function concat(a, b):string {
  return a + b;
}

Object's


// @flow

// Tell Flow that myObject must be an object and must have a name property 
// and the value must be a string using primitive type annotations

var myObject: {| name: string |} = { name: 'pat' };

Array's


// @flow

// Tell Flow that myArray must be an array and can only contain 
// number values using the number primitive type annotation

let myArray: Array<number> = [1, 2, 3];

// the above can also be written in a shorter syntax i.e. : theType[]
let mySecondArry: number[] = [1, 2, 3];

Class's


// @flow

// Tell Flow that myClassInstance must follow the types defined in TheClass

class TheClass { 
  score:number = 0
  noScoreMessage:string = 'No Score'
}

let myClassInstance: TheClass = new TheClass();

Expression's


// @flow

// Tell Flow that the expression 2+2 must result in a number value

(2 + 2: number);

Keep In Mind:

  1. Anything you don't annotate will get inferred by Flow (i.e. Flow will deduct what the type is based on how it is used or what data structures get assigned). Unless you pull the escape hatch and tell Flow to act like regular JS with the :any annotation.

4 : Basic Flow Type Annotations

This section outlines basic Flow type annotation syntax. Primitive Types, Function Types, Object Types, Array Types, Class Types, Literal Types, Maybe Types, Any Type, Mixed Type.

4.1 - JavaScript Type Types

Flow offers the following baseline annotations for the common JavaScript data type structures which I call group together and call type type annotations (i.e. these baseline types have almost a one to one relationship with native JavaScript values):

Primitive Types
Function Types
Object Types
Array Types
Class Types

Primitive Types:

A JavaScript value (e.g. variables, parameters, values returned from functions, Object properties, Array items etc...) can be annotated as a primitive value using of the following Flow type annotations:

  • : boolean
  • : number
  • : string
  • : null
  • : void (Note: void is used to mean undefined)

// @flow

let opened : boolean = false;
let score : number = 0;
let middleName : string = 'leroy';
const constantNull : null = null;
const constantUndefined : void = undefined; // Note void is used for undefined

Function Types:

A JavaScript function value can be annotated as a function type using one of the following Flow type annotations:

  • : () => void
  • : Function (Note: Don't use this, be aware however you might see its use. Instead use any or (...args: Array<any>) => any)

// @flow

// myFunc has to be a function that returns a string

let myFunc : (num : number) => string;

myFunc = (num) => {
return num.toString();
};

myFunc(34241312);

Can also be written (But really ugly and hard to read):


// @flow

let myFunc : (num : number) => string = (num) => {
  return num.toString();
};

myFunc(34241312);

Note:

  1. Using the ? character you can tell the Flow checker that a function parameter is optional. For example, let myFunction(myParam?:string){...} tells Flow that this function may or may not have a myParam parameter. You might wonder what is the different between let myFunction(myParam?:string){...} and let myFunction(myParam:?string){...} where a Maybe type is used. The difference is that a Maybe type will allow null while an optional function parameter will not allow null.

Object Types:

A JavaScript value (e.g. Object properties) can be annotated as an Object type using the following Flow type annotations (Note the properties in the Object can be typed too):

  • : { [key: string]: any} or Object
  • : {}

// @flow

let myObj1:Object = {}; // uses : Object but this is going to be deprecated

myObj1.prop = 'prop';

// Should use : { [key: string]: any} instead of : Object

let myObj2:{[key:string]:string} = {}; // uses : { [key: string]: string}

myObj2.prop = 'prop';
  

Arbitrary objects (i.e. unknown properties) that you don't set properties on can be annotated like (helpful when annotating parameters):


// @flow

const arbitraryObject = {prop:'prop'};

let myObject:{} = arbitraryObject; // uses : {}

// or as parameter

function objectToString(obj: {}) {
  return obj.toString()
}

objectToString(arbitraryObject);
    

Note:

  1. Using the ? character you can tell the Flow checker that an object might have a property or it might not. For example, let myObject : {score?:number} = {} tells Flow that this object may or may not have a score property with a number value. Optional properties can either be void or omitted altogether. However, they cannot be null.
  2. Using the | character Flow can be informed when an object must contain a specific property(s). For example, let myObject : {|score:number|} = {score:120} tells Flow that this object must have a score property with a number value. And it can't have anything less or anything more. Without the pipes extra properties can be added to objects.
  3. Flow will report an error, not just return undefined, when a property that does not exist is accessed on an object.

Array Types:

A JavaScript value can be annotated as an Array type using the following Flow type annotations (Note that the items in the Array can be typed too):

  • : Array<any> (a shorthand : any[])
  • : $ReadOnlyArray<any>

// @flow
  
let myArrayOne: Array<any> = [1, 'foo', true, null, {}, []];; 
  // a shorthand is available to the above syntax
  // let myArray: any[]; 
  
  let myArrayTw0: Array<number> = [1, 2, 3];; 
  // a shorthand is available to the above syntax
  // let myArray: number[];   

// possible to have Flow make sure certain Arrays are read only
const readonlyArray: $ReadOnlyArray<number> = [1, 2, 3];
      
  

Class Types:

To annotate a Class instance, where ever you create an instance of the class, annotate the instance with the class name (assuming annotations have been added to the Class definition) .

  • : TheClassName

// @flow

// Tell Flow that myClassInstance must follow the types defined in TheClass

class TheClass { // using https://tc39.es/proposal-class-public-fields/
  score:number = 0
  noScoreMessage:string = 'No Score'
}

let myClassInstance: TheClass = new TheClass();
  
  

4.2 - Exact Primitive (aka Literal) Type Values

Flow offers the Literal Type (e.g. const noPoints:0 = 0; ) so a data structure can be fixed to an exact primitive value or set of primitive values. The primitive values that can be used are true, false, null, void, any string (e.g. 'foo'), or any number (e.g. 1432).


// @flow
function getColorBasedOnMessageType(name: "success" | "warning" | "danger"):string {
  switch (name) {
    case "success" : return "green";
    case "warning" : return "yellow";
    case "danger"  : return "red";
  }
}

/*
Notice how the | character was used to provide an
exact set of string values
/*

getColor("success"); 
getColor("danger");  
getColor("error");   // Error, can only be "success" | "warning" | "danger"

Note that when using const a literal value is inferred.


const foo = 'bar';

// same as const foo:bar = 'bar';
  

4.3 - May or May not Exist Type Values (i.e. : ?[TYPE HERE])

Flow offers the Maybe Type (e.g. let score: ?number) that can be combined with any other type to signify that the type is optional and if it does not exist then that is ok (i.e. it is ok that null or undefined was used here).


// @flow

function acceptsMaybeNumber(value: ?number) {
  return value;
}

acceptsMaybeNumber(42);        
acceptsMaybeNumber();          
acceptsMaybeNumber(undefined); 
acceptsMaybeNumber(null);      
acceptsMaybeNumber("42");      // Error, can only be a number, undefined, or null

4.4 - Opt out of Flow Type (i.e. : any)

Flow offers the Any Type (e.g. : any) annotation to basically opt out of using the type checker. Essentially by using : any you side step the checker and allow JavaScript to do its normal thing in terms of type coercion and error'ing.


// @flow

function add(one: any, two: any): number {
return one + two;
}

add(1, 2);     // no Flow error.
add("1", "2"); // no Flow error.
add({}, []);   // no Flow error.

4.5 - Unknown Type (i.e. : mixed)

Flow offers the Unknown Type (i.e. : mixed) so that any data structure can be provided. Note that if you use the mixed type then additional checks (e.g. if statement) have to exist that will determine its type or Flow will complain (as opposed to :any which won't complain about anything)

This fails the Flow checker:


// @flow

function stringify(value: mixed) {
// $ExpectError
return "" + value; // Error!
}

stringify("foo");

/*
3:     return "" + value; // Error!
        ^ Cannot add empty string and `value` because mixed [1] could either behave like a string or like a number.
References:
1:   function stringify(value: mixed) {
                    ^ [1]
*/

But this does not:


// @flow

function stringify(value: mixed) {
if (typeof value === 'string') {
return "" + value; // Works!
} else {
return "";
}
}

stringify("foo");

// no Flow error.

5 : Advanced Flow Type Annotations

This section outlines how to organize and separate Flow types from JavaScript code

5.1 - Tuple Types

Tuples are nothing more than an Array with a fixed number of items in the Array, all having a specified Flow type.


// @flow

// Notice that a tuple has the syntax : [type, type, type] 
// don't confuse this with Array types
let myTuple: [number, boolean, string] = [1, true, "three"];

let num  : number  = myTuple[0]; 
let bool : boolean = myTuple[1]; 
let str  : string  = myTuple[2];

// These error

let none = myTuple[3]; // can't access undefined items
myTuple[3] = {}; // can't set none typed items

// Tuples don't match Arrays

let array: Array<number> = [1, 2];
let tuple: [number, number] = array; // This will error

Note:

  1. You cannot use Array.prototype methods that mutate the tuple, only ones that do not (e.g. let tuple: [number, number] = [1, 2].push(3);).

5.2 - Union Types

Union types make it possible for a value to potentially be one of a set of different Flow types. Think of them as the ability to tell Flow, a value can be this type or this type or this type or this type.


// myValue can be a string or a number or undefined
let myValue: string | number | void;

Note:

  1. Each of the members of a union type can be any type, even another union type.

5.3 - Disjoint Union Types

A "disjoint union" as Flow calls it is the intersection or overlap between two types.


type emailAndName = { name: string, email: string };
type phoneAndName  = { name: string, phone: string };

function checkWhichContactInfo(info: emailAndName | phoneAndName):string {
  if (info.email) {
    return 'email';
  } else {
    return 'phone';
  }
}

checkWhichContactInfo({name:'bill',email:'bill@hotmail.com'});

checkWhichContactInfo({name:'jill',phone:'0-000-00000'});
  
  

5.4 - Intersection Types

Intersection types take in multiple type definitions and use all of them as a type of value.


// @flow

type A = { a: number };
type B = { b: boolean };
type C = { c: string };

function method(value: A & B & C) {
  // ...
}

method({ a: 1, b: true, c: 'three' });

/* These would error

method({ a: 1 }); 

method({ a: 1, b: true });

*/
    
    

5.5 - Generic Types

6 : Organizing Types

This section outlines how to organize and separate Flow types from JavaScript code

6.2 - Type Aliases

Flow annotations do not have to be written inline. It is possible using the Flow type syntax to create an aliase to Flow annotations. This makes re-use and separation of concerns possible.

For example syntax heavy inline type definitions like this function type (i.e. inline type noise and difficult to read):


// @flow

let myFunc : (num : number) => string = (num) => {
  return num.toString();
};

let myOtherFunc : (num : number) => string = (num) => {
  return num.toString();
};

myFunc(34241312);
myOtherFunc(34241312);

Can be visually simplified, concerns separated, and made re-usable using typed aliases.


// @flow

// myFuncFlowAlias 
type myFuncFlowAlias = (num : number) => string;

// this function takes a number and returns a string 
let myFunc : myFuncFlowAlias = (num) => {
  return num.toString();
};

// this function takes a number and returns a string
let myOtherFunc : myFuncFlowAlias = (num) => {
  return num.toString();
};

myFunc(34241312);
myOtherFunc(34241312);
    
    

Flow Type aliases can be used anywhere a type annotation can be used.

Note:

  1. In my opinion all types should be aliased, removing as much noise from actual JavaScript as possible. Shared types should be imported into modules and local types should be organized below module imports.
  2. All aliases should end with the same identifier "FA" or "FlowAlias".

6.2 - Importing/Exporting Type Aliases

In Flow, you can export type aliases, interfaces, and classes from one file and import them into another file.



// @flow
export default class Foo {};
export type MyObject = { /* ... */ };
export interface MyInterface { /* ... */ };

Import:



// @flow
import type Foo, {MyObject, MyInterface} from './exports';