-
-
Save GiancarlosIO/35419bc26f00eab2da2f78f318b0650e to your computer and use it in GitHub Desktop.
Flow Fundamentals For JavaScript Developers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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.59 | |
// 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 interference. | |
*/ | |
// 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: This type is incompatible with the expected param type of number enum | |
// 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 interfere 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 furfill 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 = double('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/ | |
*/ | |
/* Read-only */ | |
/* | |
Sometimes we want to make sure that object properties are read-only. | |
Take a look at the following example: | |
*/ | |
// type AB = { a: number, b: string }; | |
// | |
// const readOnlyNone = (o: AB) => { | |
// o.a = 100 // No Error! | |
// return o | |
// } | |
/* We can rewrite our `a` propety to `+a` and ensure that `a` is read-only. */ | |
// type ReadOnlyA = { +a: number, b: string }; | |
// | |
// const readOnlyA = (o: ReadOnlyA) => { | |
// o.a = 100; // Error! | |
// o.b = 'test' // No Error! | |
// return o | |
// } | |
/* | |
Now if we want to make our object readonly, we need to make sure that all | |
properties are read-only. | |
*/ | |
// type ReadOnlyAB = { +a: number, +b: string }; | |
// | |
// const readOnlyAB = (o: ReadOnlyAB) => { | |
// o.a = 100; // Error! | |
// o.b = 'test' // Error! | |
// return o | |
// } | |
/* | |
With flow v.0.59 we can use `$ReadOnly<T>` to make an object read-only. | |
The above example can be rewritten to: | |
*/ | |
// type ReadOnlyAB = $ReadOnly<{ a: number, b: string }>; | |
// | |
// const readOnlyAB = (o: ReadOnlyAB) => { | |
// o.a = 100; // Error! | |
// o.b = 'test' // Error! | |
// return o | |
// } | |
/* | |
This is it for now. | |
This tutorial will be updated from time to time. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment