|
|
@@ -0,0 +1,637 @@ |
|
|
// @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 for 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 : string = 'foo' |
|
|
const b : number = 1 |
|
|
const c : boolean = false |
|
|
const d : number[] = [1, 2, 3] |
|
|
const e : Array<string> = ['a', 'b', 'c'] |
|
|
const f : Object = {id: 1} |
|
|
const g : null = null |
|
|
const h : void = 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 let flow interfer the code. |
|
|
|
|
|
*/ |
|
|
|
|
|
// 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 nullable. |
|
|
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' |
|
|
|
|
|
/* |
|
|
We were dealing 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 let or var variable any re-assignment |
|
|
has to be of that same type otherwis Flow will complain. |
|
|
*/ |
|
|
|
|
|
/* |
|
|
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' |
|
|
|
|
|
/* |
|
|
|
|
|
Take a look at the following example: |
|
|
--------------------------------------------------------------------- |
|
|
|
|
|
const i : 3 = 2 |
|
|
^ number. Expected number literal `3`, got `2` instead |
|
|
|
|
|
--------------------------------------------------------------------- |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
// Any Vs. Mixed |
|
|
|
|
|
/* |
|
|
Sometime 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 = 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 foo = length(1) // no Error! |
|
|
// const bar = length('foo') |
|
|
// const baz = length([1, 2, 3, 4]) |
|
|
|
|
|
|
|
|
// Optional Values |
|
|
|
|
|
/* |
|
|
Sometimes we want to a certain value 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 see 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 in the input and output of a function, 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 add 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. |
|
|
*/ |