Learning flow

Learning Flow

Flow uses a static type annotation to check your code. The idea is for you to get some of the benefits of a typed language in Javascript. To be able to code with greater confident, knowing that your types work as they should.

To test run all code here you can grab flow.js

Usage

Install Flow via npm.

Then do flow init to init your project and flow status to check for type errors. You add // @flow to the top of a file to Flow to check it.

That’s it, you should be up and running.

Annotations

Javascript types

Flow supports all Javascript types.

  • Booleans
  • Strings
  • Numbers
  • null
  • undefined (except it’s declared as void in Flow)

If the values appear as primitive values, "foobar", they’re declared with lowercase: string. If they’re constructed, new String("foobar"), they’re declared Capitalized: String.

function method(x: number, y: string, z: boolean) {}
function method2(x: Number, y: String, z: Boolean) {}

method(3.14, 'hello', true) // Ok
method('hello', true, 3.14) // Errors

method2(new Number(3.14), new String('hello'), new Boolean(true)) // Ok
method2(3.14, 'hello', true) // Errors
method(new Number(3.14), new String('hello'), new Boolean(true)) // Errors

Mixed Types

At times we don’t know what type will be passed to our function. This is not something we’d want to use often but at times it’ll be useful.

function stringifyMixedReturnString(value: mixed): string {
  if (typeof value === 'string') {
    return "String:" + value
  } else if (typeof value === 'number') {
    return "Number:" + value
  } else {
    return `Something else: ${String(value)}`
  }
}
print(stringifyMixedReturnString("hello")) // Ok
print(stringifyMixedReturnString(42)) // Ok
print(stringifyMixedReturnString(true)) // Ok

Literal Types

One good use of mixed types could be in combination with literal types. Instead of mixing types you can mix literals. This example

function typeLookup(type: 'success' | 'warning' | 'danger') {
  return {
    success: 'green',
    warning: 'yellow',
    danger: 'red'
  }[type]
}

print(typeLookup('success'))
function typeLookup(type: 0 | 1 | 2) {
  return ['green', 'yellow', 'red'][type]
}
print(typeLookup(1))

Any Types

Don’t use this, it’s bad.

Maybe Types

By prefixing any other type with ? you tell Flow that this is an optional argument.

function multiply(value: ?number) {
  if (value !== undefined && value !== null) {
    return value * value
  }

  return 0
}

print(multiply())
print(multiply(10))

Using the types

The types can be used in 3 different places. Variables can have a type. Functions arguments can have types and functions can have a return type.

Variable types

You can give a variable a type in Flow. Once a variable have a type it’ll make sure it doesn’t change.

let isUndefined = 'foo' // Ok
isUndefined = 10 // Ok

let str = 'foo'
let isUndefined2 = str // Ok
let isStr: string = str // Ok
let isNumber: number = str // Error
let isMixed: number | string = str // Ok

let newStr: string
newStr = 'bar' // Ok
newStr = 10 // Error

Function types

Argument types have been shown above already. Functions return types look like this.

function func(): string {
}

const func2 = (): string => {}

Object types

The syntax for object types might look a bit weird at first but it’s actually rather similar to the rest.

let typeLookupObj: {
  success: string,
  warning: string,
  danger: string
} = {
  success: 'green',
  warning: 'yellow',
  danger: 'red'
}
typeLookupObj.warning // Ok
typeLookupObj.error // Error

Trying to access an undefined object property will generate an error.

You can use the Maybe type within objects for properties that might not always be defined.

Array types

Lets skip over the verbose way of defining array types, I don’t like it. The shorthand looks like this.

let numberArray: number[] = [0, 1, 2, 3]
let stringArray: string[] = ['a', 'b', 'c']

let value: string = stringArray[0] // Ok
print(value)
let value2: string = stringArray[10] // Ok, even if it shouldn't be
print(value2)

Just remember that array access is unsafe. Any access to the array outside of the defined elements wills till return Ok.

Tuple types

This is a similare type to the Array type, except here we define every single elements type. The tuple lenght is enforced by Flow.

let tuple: [number, string, boolean] = [1, "foo", true]

tuple[0] = 1 // Ok
tuple[0] = "foo" // Error

let tuple2: [number, string] = tuple // Error

Class types

Flow works well with es6 classes. Flow is a nominal type system. Meaning even if two classes have the same structure you must use the correct class name when typing them. In some cases you might want to use the same type for two different classes, this is used with an interface.

class AFancyClass {
  stringProp: string
  numberProp: number

  someFunction() {
    this.stringProp = 'foo' // Ok
    this.numberProp = 10 // Ok
    this.booleanProp = true // Error
  }
}

let aFancyClass: AFancyClass = new AFancyClass()

Type Aliases

To simplify type definitions you can break them out to type aliases. These aliases can also be reused in multiple places.

type MyType = {
  foo: number,
  bar: boolean,
  baz: string
}

type ComplexType = MyType | [number, string]

Interface types

An interface works much like a type alias but for classes. Instead of defining object types we define function return types.

interface Language {
  print(): string
}

class English {
  print() {
    return 'Hello'
  }
}

class Swedish {
  print() {
    return 'Hej'
  }
}

const english: Language = new English() // Works!
const swedish: Language = new Swedish() // Works!

class Finnish implements Language {
  print() {
    return 'Hei'
  } // Works!
}

class Icelandic implements Language {
  print() {
    return 42
  } // Error!
}

class Icelandic2 implements Language {
  echo() {
    return 'Halló'
  }
} // Error!

Generic types

Sometimes it’s not useful to define a type for an argument, but you still want to make sure it still adhere to a form.

function myFunction<T>(value: T): {value: T} {
  return {value};
}

Union types

You can provide a list of different types to a function, a union type. In general this would be something you want to avoid. But at times it’s a must but then it can be useful to at least limit the accepted types to a few different.

When creating a union type you need to make sure you handle all types.

type unionA = number
type unionB = string

function stringifyUnionValue(arg: unionA | unionB) {
  return '' + arg
}
print(stringifyUnionValue('foobar')) // Ok
print(stringifyUnionValue(10)) // Ok
print(stringifyUnionValue(true)) // Error

Intersection types

Instead saying that a type can be type a | type b it can be useful to say that it has to be both type a & (and) type b.

type intersectionA = {number: number}
type intersectionB = {string: string}

function stringifyIntersectionValue(arg: intersectionA & intersectionB) {
  return `${arg.number}  ${arg.string}`
}
print(stringifyIntersectionValue({string: 'foobar', number: 123})) // Ok
print(stringifyIntersectionValue({number: 10})) // Error
print(stringifyIntersectionValue({value: true})) // Error

Extra functionality

typeof

You can use typeof on types.

let typeofNumber = 42;
let typeofNumber2: typeof typeofNumber = 3.14;    // Ok

Type casting

You can cast a value by doing (value: Type). This could be useful if you’ve use the any type before and want to cast later on for example.

$Keys<T>

This is a useful way to get a union of an objects keys.

const lookupKeys = {
  FOO: "Some key",
  BAR: "Another key",
  FOOBAR: "Yet another key"
}

type Keys = $Keys<typeof lookupKeys>
const italy: Keys = 'IT' // Ok
const nope: Keys = 'nope' // Error

$Diff<A, B>

Gives you the diff between two types. However this doesn’t work exactly like I’d want. Since any extra props are ok, even those explicitly not in the diff. The only thing this gives us is that the diff is required.

type PropsA = { stringProp: string, numberProp: number }
type PropsB = { numberProp: number }
type DiffProps = $Diff<PropsA, PropsB>

function setProps(props: DiffProps) { }

setProps({ stringProp: 'foo' }) // Ok
setProps({ stringProp: 'foo', numberProp: 42, extraProp: false }) // Ok
setProps({ numberProp: 42 }) // Error