Skip to content

Instantly share code, notes, and snippets.

@mikaelbr
Last active April 23, 2018 09:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikaelbr/26cd33943b50b127d6e5fcc21eb448d5 to your computer and use it in GitHub Desktop.
Save mikaelbr/26cd33943b50b127d6e5fcc21eb448d5 to your computer and use it in GitHub Desktop.

Table of Contents

  1. Basic syntax
  2. Flow Examples
  3. Advanced features
  4. Using with existing libraries (flow-typed and libdefs)
  5. Tooling
  6. Try

1. Basics

1. Primitives (++).

// @flow

(1: number);
('foo': string);
(true: boolean);
(null: null);
(undefined: void);
(undefined: ?string);
(undefined: ?number);
([1, 2, 3]: Array<number>);
([1, 2, 3]: number[]);
([1, 'dsa', 3, null]: mixed[]);
({ foo: 'bar' }: { foo: string });
({ foo: 'bar', bar: 1 }: { foo: string });

// Annotating variables
let a: number = 1; // <- Allowed
let b: string = 1; // <- Not allowed
let c: number | string = 1; // <- Allowed

// Inferring types
let m = 1;
m = 'foo';
(m: string); // <- Allowed
(m: number); // <- Not allowed
(m: boolean); // <- Not allowed

// Literal types
(1: 1); // <- Allowed
(1: 'foo'); // <- Not Allowed
('foo': 'foo'); // <- Allowed
('foo': true); // <- Not Allowed
(true: true); // <- Allowed
({ foo: 'bar' }: { foo: 'bar' }); // <- Allowed
({ foo: 'bar' }: { foo: 'foo' }); // <- Not Allowed

2. Annotating functions

Functions are structural types.

// @flow

function add(a: number, b: number): number {
  return a + b;
}
add(2, 4); // <- Allowed
add('foo', 4); // <- Not Allowed

let double = (n: number): number => n * 2;
double(4); // <- Allowed
double('foo'); // <- Not Allowed

function withCallback(foo: Function) {
  // Same as any
  return foo();
}

withCallback(() => 5); // <- Allowed
withCallback(() => ({})); // <- Allowed
withCallback(() => 'str'); // <- Allowed

// With type annotation

function withCallback2(
  foo: (a: number, b: number) => number
) {
  return foo(5, 2);
}

withCallback2(add); // <- Allowed
withCallback2((a, b) => a + b); // <- Allowed
withCallback2(a => 'foo'); // <- Not Allowed

// Without named parameter

function withCallback3(foo: (number, number) => number) {
  return foo(5, 2);
}

withCallback3(add); // <- Allowed
withCallback3((a, b) => a + b); // <- Allowed
withCallback3(a => 'foo'); // <- Not Allowed

// Own type
type Callback = (number, number) => number;
function withCallback4(foo: Callback) {
  return foo(5, 2);
}

withCallback4(add); // <- Allowed
withCallback4((a, b) => a + b); // <- Allowed
withCallback4(a => 'foo'); // <- Not Allowed

3. Objects and Type Aliases

Objects are structural types.

// @flow

let myObj: {
  foo: Array<number>
};

myObj = { foo: [1, 2, 3] }; // <- Allowed
myObj = { foo: [1, 2, 3], bar: 'dsa' }; // <- Allowed
myObj = { bar: 'dsa' }; // <- Not Allowed

// Type Aliase

type MyObject = {
  foo: Array<number>
};

let myObj2: MyObject;

myObj2 = { foo: [1, 2, 3] }; // <- Allowed
myObj2 = { foo: [1, 2, 3], bar: 'dsa' }; // <- Allowed
myObj2 = { bar: 'dsa' }; // <- Not Allowed

4. Interface and Class

Interfaces are structurally typed, classes are nominal.

// @flow

interface JSONSerializable {
  toJSON(): string
}

class Foo {
  toJSON() {
    return '';
  }
}
class Bar {
  toJSON() {
    return '';
  }
}

([new Foo(), new Bar()]: JSONSerializable[]); // <- Allowed
([new Foo(), new Bar()]: Foo[]); // <- Not Allowed

class Foo2 implements JSONSerializable {
  toJSON() {
    return JSON.stringify({ hello: 1 });
  }
}

class Bar2 implements JSONSerializable {
  toJSON() {
    return JSON.stringify(42);
  }
}

With objects:

// @flow

interface JSONSerializable {
  toJSON(): string,
  foo: number
}

const myObject = {
  toJSON() {
    return '';
  },
  foo: 1
};

(myObject: JSONSerializable); // <- Works

5. Imports and exports

// exports.js

// @flow
export type MyObject = {
  foo: Array<number>
};

export interface JSONSerializable {
  toJSON(): string
}

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


// imports.js

// @flow

import type { MyObject, JSONSerializable } from './exports';
import typeof add from './exports';

const myAdd: add = (a, b) => a + b;

type Intersected = MyObject & JSONSerializable;

let myObject: Intersected = {
  foo: [1, 2, 3],
  toJSON() {
    return '';
  }
}; // <- Not allowed

({}: Intersected); // <- Not allowed
({ foo: [1] }: Intersected); // <- Not allowed
({ foo: [1], toJSON: 1 }: Intersected); // <- Not allowed

2. Flow Examples

1. Optionals types not automatically safe:

function prefix(data: ?string) {
  if (!data) return 'No prefix';
  return data + ' <- prefix!';
}

console.log(prefix('Foo'));
console.log(prefix('')); // No prefix
console.log(prefix()); // No prefix

Also not the same as optional argument:

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

double(1, 2); // <- Allowed
double(void 0, null); // <- Allowed
double(3, null); // <- Allowed
double(null, null); // <- Not allowed

Best to avoid null and if you have to do overloading, use defaults?

function double(a: number = 1, b: number = 2) {
  return a + b;
}

double(1, 2);
double(void 0, void 0);
double(void 0, 3);
double(3);

2. Support for literal types as with annotated types:

let add = (a: 1, b: 2): number => a + b;
add(1, 2); // <- Not allowed

Can be used as enums:

type Action = 'DO_SOMETHING' | 'DO_SOMETHING_ELSE';
let dispatch = (action: Action) => action;
dispatch('DO_SOMETHING'); // <- Allowed
dispatch('ADD_NUMBERS'); // <- Not allowed

It's not exhaustive, but it makes sure you don't check for impossible code:

type Action = 'DO_SOMETHING' | 'DO_SOMETHING_ELSE';
function reducer(action: Action) {
  switch (action) {
    case 'Foo': // <- Not allowed
      return 1;
    case 'DO_SOMETHING':
      return 1;
    // <- Not exhaustive
  }
}
reducer('DO_SOMETHING'); // <- Allowed
dispatch('Foo'); // <- Not allowed

But we can do a workaround/hack:

type Action = 'DO_SOMETHING' | 'DO_SOMETHING_ELSE';
function reducer(action: Action): number {
  switch (action) {
    case 'DO_SOMETHING':
      return 1;
    case 'DO_SOMETHING_ELSE':
      return 2;
    default:
      return action; // <- Makes it an exhaustive list... :D (maybe not the best idea?)
  }
}
reducer('DO_SOMETHING'); // <- Allowed

3. Types based on other types

function identity<T>(a: T): T {
  return a;
}
identity('a'); // <- Allowed
identity(3); // <- Allowed
let foo: string = identity(3); // <- Not Allowed

4. any != mixed

function add(one: mixed, two: mixed): mixed {
  return one + two; // <- Not Allowed
}

But

function add(one: any, two: any): any {
  return one + two; // <- Allowed
}

5. Object types and width subtyping

type Actions = 'FOO' | 'BAR';
type Action = { name: Actions };

function dispatch(action: Action) {
  return action;
}

dispatch({ name: 'FOO', load: [1, 2, 3] });

Exact type:

type Actions = 'FOO' | 'BAR';
type Action = {| name: Actions |};

function dispatch(action: Action) {
  return action;
}

dispatch({ name: 'FOO', load: [1, 2, 3] }); // <- Not allowed

6. Types for maps

var o: { [string]: number } = {};
o["foo"] = 0;
o["bar"] = 1;
var foo: number = o["foo"];

7. Union types & Intersection types.

Can also intersect types:

type Actions = 'FOO' | 'BAR';
type Load = { load: Array<number> }
type Action = { name: Actions };
type ActionLoad = Action & Load;

function dispatch(action: ActionLoad) {
  return action;
}

dispatch({ name: 'FOO', load: [1, 2, 3] }); // <- Allowed
dispatch({ load: [1, 2, 3] }); // <- Not Allowed
dispatch({ name: 'FOO' }); // <- Not Allowed

Doesn't work with exact object types (would be null type) or primitives:

type Actions = 'FOO' | 'BAR';
type Load = {| load: Array<number> |}
type Action = {| name: Actions |};
type ActionLoad = Action & Load;

function dispatch(action: ActionLoad) {
  return action;
}

dispatch({ name: 'FOO', load: [1, 2, 3] }); // <- Not Allowed
dispatch({ load: [1, 2, 3] }); // <- Not Allowed
dispatch({ name: 'FOO' }); // <- Not Allowed

But can spread types:

type Actions = 'FOO' | 'BAR';
type Load = {| load: Array<number> |}
type Action = {| name: Actions |};
type ActionLoad = {| ...Action, ...Load |};

function dispatch(action: ActionLoad): string {
  return action.name;
}

dispatch({ name: 'FOO', load: [1, 2, 3] }); // <- Allowed
dispatch({ name: 'FOO', load: [1, 2, 3], extra: 'dsa' }); // <- Not Allowed

8. Can typeof types

type Actions = 'FOO' | 'BAR';
type Load = {| load: Array<number> |};
type Action = {| name: Actions |};
type ActionLoad = {| ...Action, ...Load |};

let action: ActionLoad = { name: 'FOO', load: [1, 2, 3] };
let action2: typeof action = { foo: 1 }; // <- Not allowed

9. Type checking through type assertions

function clone(a: Array<mixed>) {
  return ((a.slice(): any): typeof a);
}

let r = clone([1, 2, 3]);

(r[0]: 1); // <- Doesn't work

But:

// @flow

function clone(a) {
  (a: Array<mixed>);
  return ((a.slice(): any): typeof a);
}

let r = clone([1, 2, 3]);

(r[0]: 1); // <- Works

Better solution:

// @flow

function clone<T: Array<mixed>>(a: T): T {
  return ((a.slice(): any): typeof a);
}

let r = clone([1, 2, 3]);

(r[0]: 1); //<- Allowed
(r[1]: 3); //<- Not Allowed

3. Advanced features

$Keys<T>

$Diff<A, B>

$PropertyType<T, x>

// @flow
import React from 'react';
class Tooltip extends React.Component {
  props: {
    text: string,
    onMouseOver: ({x: number, y: number}) => void
  };
}

const someProps: $PropertyType<Tooltip, 'props'> = {
  text: 'foo',
  onMouseOver: (data: {x: number, y: number}) => undefined
};

const otherProps: $PropertyType<Tooltip, 'props'> = {
  text: 'foo'
  // Missing the `onMouseOver` definition
};

4. Using with existing libraries (flow-typed and libdefs)

Use flow-typed or define library definitions manually in /flow-typed (from root) for Flow to find definitions for third party code.

5. Tooling

TODO

6. Try

https://flow.org/try/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment