Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Flow Fundamentals For JavaScript Developers
// @flow
// Flow Fundamentals For JavaScript Developers
/*
Tutorial for JavaScript Developers wanting to get started with FlowType.
Thorough walkthrough of all the basic features.
We will go through the basic features to gain a better understanding of the fundamentals.
You can uncomment the features one by one and work through this tutorial.
If you have any questions or feedback please connect via Twitter:
A. Sharif : https://twitter.com/sharifsbeat
*/
// Current Version: Flow v0.55
// SETUP
/*
* Always refer to the official documentation for a deeper understanding.
https://flow.org/en/
* Installation and Setup:
https://flow.org/en/docs/install/
* Online Try Repl:
https://flow.org/try/
* IDE Plugins:
Setup Flow for your IDE
https://flow.org/en/docs/editors/
*/
// Basics
/*
Let's begin with a couple of very basic examples.
*/
const a = 'foo'
const b = 1
const c = false
const d = [1, 2, 3]
const e = ['a', 'b', 'c']
const f = {id: 1}
const g = null
const h = undefined
/*
Flow offers a number of types.
To get a quick overview, let's be explicit and define types for the above constants.
`const a : string = 'foo'` (ok)
---------------------------------------------------------------------
`const a : number = 'foo'` (error)
We will get the following error:
const a : number = 'foo'
^^^^^ string. This type is incompatible with
---------------------------------------------------------------------
So assign the type for learning purpoeses. In reality you would rely on type inference.
*/
// const aTyped : string = 'foo'
// const bTyped : number = 1
// const cTyped : boolean = false
/*
The first three are relatively clear. But how do we type `d`?
*/
// const dTyped : number[] = [1, 2, 3]
// or
// const dTyped : Array<number> = [1, 2, 3]
/*
Let's continue:
*/
// const eTyped : Array<string> = ['a', 'b', 'c']
// const fTyped : Object = {id: 1}
// const gTyped : null = null
/*
What about undefined?
In FlowType you can use `void` type to declare a value as undefined.
const h : void = undefined
We will get into more detail as we progress and cover primitives in more detail.
*/
const i = 2
const j = 'foo'
/*
Let's continue and see what else FlowType has to offer.
For example the above `i` and `j` could either be assigned a primitve type, but interestingly
a literal type as well. How would that look like?
*/
// const iTyped : 2 = 2
// const jTyped : 'foo' = 'foo'
/*
Now you might be wondering what value we gain from literal types?
We can constraint what values we expect.
*/
// type ExpectedInput = 1 | 2 | 3
//
// const doSomething = (input: ExpectedInput) => {
// switch (input) {
// case 1: return 'Level 1'
// case 2: return 'Level 2'
// case 3: return 'Level 3'
// }
// }
//
// doSomething(0) // error
// doSomething(1) // ok
/*
We were dealing with const variables up until now. What about let or var. While
const variables can't be reassigned, which means FloeType can infer the type and know for sure it will never change.
This is different with let or var.
*/
// let aVar : string = 'foo'
//
// aVar = 'bar'
// aVar = 1 // Error!
/*
As we can see in the above example, once you assign a type to a let or var variable any re-assignment
has to be of that same type otherwise Flow will complain.
*/
/*
Take a look at the following example:
---------------------------------------------------------------------
const i : 3 = 2
^ number. Expected number literal `3`, got `2` instead
---------------------------------------------------------------------
*/
// Any Vs. Mixed
/*
Sometimes you can't tell what the exact type is or you are currently converting an existing
non-typed code base. Here is where `any` and `mixed` are helpful. It's important to note
that they fulfill different puroposes. `any` should be used as a last resort, as it skips type checking.
In contrast `mixed` is useful when you can't be sure what the input type is, i.e:
*/
// const double = (input: mixed) => {
// if (typeof input === 'string') {
// return input + ' - ' + input
// }
// if (Array.isArray(input)) {
// return input.concat(input)
// }
// return input
// }
//
// const result = doSomething('foo') // ok
/*
We need to refine the input by checking the type and then returning an appropriate value else Flow will complain.
With `any` we completely bypass the type checker. We can pass in any value to `length` and will never receive an error.
As already mentioned use `any` as a last resort if possible.
*/
// const length = (input: any) => {
// if (typeof input === 'string') {
// return input.length
// }
//
// if (Array.isArray(input)) {
// return input.length
// }
//
// return false
// }
//
// const bar = length('foo')
// const baz = length([1, 2, 3, 4])
// const foo = length(1) // no Error!
// Optional Values
/*
Sometimes we want a certain value to be optional.
For example take a look at the following function:
*/
// const optionalLength = (input: ? string | Array<any>) => {
// if (typeof input === 'string') {
// return input.length
// }
//
// if (Array.isArray(input)) {
// return input.length
// }
//
// return false
// }
// optionalLength()
// optionalLength(null)
// optionalLength(undefined)
// optionalLength([1, 2, 3, 4])
// optionalLength('foo')
/*
As we can see, we can call optionalLength with undefined, null, an array or a string.
But as you would expect, passing in a number would cause an error.
*/
// optionalLength(1) // Error!
// Functions
/*
Now that we have covered the very basics, it's time to get more advanced.
We have already seen a couple of functions in the previous section, but let's take a more
detailed look at Function types.
First off all, we would like to type the input and output of a function, so let's see
how this is done.
*/
// let add = (a: number, b: number) : number => {
// return a + a
// }
// add(2, 2)
//add(2, 'foo') // Error!
// const addResult : number = add(2, 2)
/* Try running the follwing: */
//const addResultError : string = add(1, 2)
/*
You will see the following error:
---------------------------------------------------------------------
const addResultError : string = add(1, 2)
^^^^^^^^^ number. This type is incompatible with
const addResultError : string = add(1, 2)
^^^^^^ string
---------------------------------------------------------------------
*/
/*
In case you want to use tradional function declarations as opposed to arrow functions,
you can write the previous example like so:
*/
// function addFunction(a: number, b: number) : number {
// return a + a
// }
//addFunction(2, 2)
//addFunction(2, 'foo') // Error!
//const addFunctionResult : number = addFunction(2, 2)
/*
For more information check:
https://flow.org/en/docs/types/functions/
*/
// Arrays
/*
Let's continue with arrays.
If you recall at the very beginning, we typed a simple array. There are two ways to type an array:
Array<Type> or Type[].
So f.e. these two are equivalent:
*/
// const aArray : Array<number> = [1, 2, 3]
// const aArrayShortHand : number[] = [1, 2, 3]
/*
What if we might have a null value inside our array. The answer is very similar to
what we have seen in the Optional section.
*/
// const aOptionalArray : Array<?number> = [1, null, 2, undefined]
// const aOptionalArrayShortHand : (?number)[] = [1, null, 2, undefined]
/*
What if we want to be more specific with our array definition?
Take the follwing example:
We have an array consisting of exactly three items, in short a tuple containg a string, a number and another string:
['foo', 1, 'bar']
*/
// const tupleA : [string, number, string]= ['foo', 1, 'bar']
// const tupleB : [string, number, number]= ['foo', 1, 'bar'] // Error!
/*
Another important aspect is that once you have a tuple defined, you can't use any
of the existing Array methods which mutate the array. Flow will complain at the next example:
*/
// tupleA.push('foobar')
/*
---------------------------------------------------------------------
tupleA.push('foobar')
^^^^ property `push`. Property not found in
tupleA.push('foobar')
^^^^^^ $ReadOnlyArray
---------------------------------------------------------------------
So once you define a tuple it becomes read-only as opposed to an array, which can be
observed quite well via the next code snippet:
*/
// const bArray : Array<number> = [1, 2, 3]
// bArray.push(4)
// bArray.push('foo') // Error!
/*
For more information check:
https://flow.org/en/docs/types/arrays/
https://flow.org/en/docs/types/tuples/
*/
// Objects
/*
Let's take a look at how Flow works with objects.
*/
// const aObject : Object = {id: 1, name: 'foo'}
// const bObject : {id: number} = {id: 1, name: 'foo'}
// const cObject : {id: number, name: string} = {id: 1, name: 'foo'}
//const dObject : {id: number, name: string, points: number} = {id: 1, name: 'foo'} // Error!
/*
`dObject` will casue an error as points is not defined. We want to make points optional.
We've already seen how to make a value optional, so let's see how to achieve the same for
an object property.
*/
// const dRefinedObject : {id: number, name: string, points?: number} = {id: 1, name: 'foo'}
/*
By declaring `points?: number`, we are saying that points might not be defined.
To make things more readible, you will probably resort back to defining a type alias
for the object declaration. This is especially helpful if you also plan to reuse a type definition.
*/
// type E = {id: number, name: string, points?: number}
// const eObject : E = {id: 1, name: 'foo'}
/*
Another important thing to note when working with objects and Flow, is that there is
a concept of sealed vs. unsealed objects. Take a look at the following code snippets:
*/
// const fObject = {
// id: 1
// }
//
// fObject.name = 'foo' // Error!
/*
So the above doesn't work. Per definition a sealed object is an object with defined properties.
An unsealed object is defined as `{}`, an object containing no properties.
Now we can add a property the object, without FlowType complaining.
*/
// const gObject = {}
// gObject.name = 'foo'
/*
Another important aspect when working with objects is that we don't have to be exact with
our type definition. See the next example:
*/
// type F = {id: number, name: string}
// const fObject : F = {id: 1, name: 'foo', points: 100} // No Error!
/*
But what if wanted to be exact?
We can be exact by wrapping our definition inside `{|...|}` like so:
*/
// type G = {|id: number, name: string|}
// const gObject : G = {id: 1, name: 'foo', points: 100} // Error!
// const gOtherObject : G = {id: 1, name: 'foo'} // No Error!
/*
Another possibility is to use the `$Exact<T>` utility helper provided by Flow.
*/
// type H = $Exact<{id: number, name: string}>
// const hObject : H = {id: 1, name: 'foo', points: 100} // Error!
/*
A common approach in JavaScript is to use objects as a map, again FlowType offers
a convenient way to type a map.
*/
// const aMap : {[number] : string} = {}
// aMap[1] = 'foo'
//aMap['a'] = 'foo' // Error!
//aMap[1] = 1 // Error!
// const otherMap : {[string]: number} = {}
// otherMap['foo'] = 1
// otherMap[1] = 2 // Error!
// otherMap['bar'] = 'foo' // Error!
/*
We can also mix property declarations with dynamic key/value pairs:
*/
// const mixedMap : {
// id: number,
// [string]: number
// } = {
// id: 1
// }
//
// mixedMap['foo'] = 1
// mixedMap[1] = 2 // Error!
// mixedMap['bar'] = 'foo' // Error!
/*
For more information check:
https://flow.org/en/docs/types/objects/
*/
// Classes
/*
There is not much needed to know to be able to type classes with Flow.
You can refer to the class itself when typing a class instance.
*/
//
// class Foo {
// state = {val: 0}
// update(val) {
// this.state = {val}
// }
// getVal() {
// return this.state.val
// }
// }
//
// const foobar : Foo = new Foo()
/*
Class methods and properties can be typed like you would expect.
Let's update the previous example.
*/
// class Foo {
// state : {val: number} = {val: 0}
// update(val:number) : void {
// this.state = {val}
// }
// getVal() : number {
// return this.state.val
// }
// }
//
// const foobar : Foo = new Foo()
//
// foobar.update(3)
// foobar.update('foo') // Error!
//
// const fooResult : number = foobar.getVal()
// const fooResultError : string = foobar.getVal() // Error!
/*
To round things off, let's also take a look at interfaces.
What if we had a class Bar, that also had a state property and an update function?
*/
// interface Updateable<T> {
// state: {val: T};
// update(a: T) : void
// }
//
// class Bar implements Updateable<boolean> {
// state = {val: false}
// constructor(val) {
// this.state = {val}
// }
// update(val) {
// this.state = {val}
// }
// getValue() {
// return this.state.val
// }
// }
// const barClass = new Bar(true)
// const barClassResultOk : boolean = barClass.getValue()
// const barClassResultError : number = barClass.getValue() // Error!
/*
How would you implement the new Foo class that implements Updateable?
This a little exercise for the interested reader.
*/
/*
For more information check:
https://flow.org/en/docs/types/classes/
https://flow.org/en/docs/types/interfaces/
*/
// Generics
/*
Now we're getting into more advanced territory here. Up until now we should
have covered all the necessary basics.
Let's continue with our previous example and add generics. For example our Foo class might als
accept a string instead of a number. We want to abstract the type definition in this case.
*/
// class Foo<A> {
// state : {val: A}
// constructor(input: A) {
// this.state = {val: input}
// }
// update(val:A) : void {
// this.state = {val}
// }
// getVal() : A {
// return this.state.val
// }
// }
//
// const fooGenericNumber : Foo<number> = new Foo(1)
// fooGenericNumber.update(2)
// const fooGenericResult : number = fooGenericNumber.getVal()
//
// const fooGenericString = new Foo('foo')
// const fooGenericResultOther : string = fooGenericString.getVal()
/*
If you uncommented the above example you will notice that everything works.
Interestingly you don't even have to explicity define a type for `const fooGenericString = new Foo('foo')`
Flow will know that our return value is a string, as can be seen in the following line.
We can do a lot more with generics, like f.e. define type aliases or functions.
Let's see some examples to get a better idea of the possibilities.
*/
// type FooBar<A, B> = {
// one: A,
// two: B
// }
//
// const GenericAlias : FooBar<number, boolean> = {
// one: 1,
// two: false
// }
//
// const GenericAliasError : FooBar<number, boolean> = {
// one: 1,
// two: 'foo'
// } //Error!
/*
Generics with Functions
*/
// const doubleIfPossible = <A>(a: A) : A => {
// if (typeof a === 'string' || typeof a === 'number') {
// return a + a
// }
// return a
// }
//
// const doubleIfPossibleResultOne : number = doubleIfPossible(2)
// const doubleIfPossibleResultTwo : string = doubleIfPossible('foo')
// const doubleIfPossibleResultError : string = doubleIfPossible(true) // Error!
/*
There is a lot more you can do with generics. If you're interested to find out more, then consult
the official Flow documentation.
*/
/*
For more information check:
https://flow.org/en/docs/types/generics/
*/
/*
This is it for now.
This tutorial will be updated from time to time.
*/
@srdjan

This comment has been minimized.

Copy link
Owner Author

@srdjan srdjan commented Oct 3, 2017

small spelling fixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.