path | title |
---|---|
/learnings/javascript_flow |
Learnings: Javascript: Flow Transpiler |
Flow is Facebook's transpiler that adds (just) compile time type checking to Javascript.
function concat( a: string, b: string ): string {
return a + b
}
concat("hello", " world"); // <-- compiles!
concat("hello", 42) // <-- will not, even though Javascript would have auto-cast it to work...>
Goals: precision, speed.
Objects are structurally typed: objects with same field names and field types are considered the same.
Classes are nominally typed: because they have different names they are not the same.
Can make <<Learning_Javascript_Flow_Interfaces>> by making an object have the method signatures you want, like so
type Consumer = {
apply(value: string): void;
}
class myRunner {
apply( value: string): void;
}
class SecondRunner {
apply( value: string): void;
}
let test: Interface = new SecondRunner()
Note: Flow has a utility type for functional interfaces / higher level functions.
Because objects are checked by structure, we don't have to cast into a read only type:
type Props = {
name: string,
age: number,
};
type ReadOnlyProps = $ReadOnly<Props>;
function render(props: ReadOnlyProps) {
const {name, age} = props; // OK to read
props.name = "hi" // Error when writing
}
let mine : Props = {name: "hello", age: 42}
render( mine )
When you're in an if
statement you know the thing you just checked for is true: you have a more refined view of your object now.
Flow takes this a step further: if you pass a value into a function in your if statement, Flow can't know if that function has mutated its value or not. (One way to get around this is to do a copy).
function concat( a: string, b: ?string, c: string) {
}
concat("a", "b", "c"); // compiles
concat("a", null, "bobby") //compilers!!!
function concat( a: string, b: string, c?: string) {
}
concat( "hello", "world", " "); // works!
concat("hello", "world"); // works!!
function concat( a: string, b: string, joiner: " " | ", ") {
}
concat("hello", "world", " ") // compiles!
concat("hello", "world", "!") // errors!
Promises are annoying, and I had to jump into the Flow source code to understand them.
let output: Promise<string> = new Promise( (resolve, reject) => resolve("hi") )
That will compile if you are not using the flow plugin for ESLINT. It will throw a linter error if you are, because resolve
and reject
don't have type annotations.
If you are like me and:
- you want to use the flow plugin for ESLint
- ... or... you want to avoid confusing the ordering of these two function callbacks
Then there is a solution here. It is a lot of typing, so I'm not 100% sure about it, but it does compile (and, more importantly, fail if you have the orders mixed up!)
type PromiseResolveType<R> = (Promise<R> | R) => void;
type PromiseRejectType = (error: Error) => void;
let output: Promise<string> = new Promise( (resolve: PromiseResolveType<string>, reject: PromiseRejectType) => { ... } )
This took a bit of looking, and in fact browsing the Flow open source code to figure out what the Promise type declaration