- Notifications
You must be signed in to change notification settings - Fork0
Differences between Flowtype and TypeScript -- syntax and usability
License
Bjeaurn/typescript-vs-flowtype
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Both TypeScript and Flow are very similar products and they share most of their syntax with some important differences.In this document I've tried to compile the list of differences and similarities between Flowtype and TypeScript -- specifically the syntax, usage and usability.
This document might be incomplete and/or contain mistakes and was last updated to describeTypeScript 3.2 andFlow 0.86.
I'm maintaining it in my spare time, so if you find mistakes, or learn about latest additions to either project, please help keep this repo up-to-date by contributing andediting this page.
Thanks!
Some of these differences are subjective (e.g. error readability), and I'd love to make this as scientific as possible — so pleasecontribute to make it better. :)
| TypeScript | Flow | |
|---|---|---|
| Leading Design Goal / North Star | identify errors in programs througha balance between correctness and productivity | enforce type soundness / safety |
| IDE integrations | top-notch: language server, built-in refactorings, type and typedoc information on hover, snappy go-to-definition | language server is a work in progress, some IDEs use the CLI and require saving the file to run the type-check, refactorings in alpha, only type information on hover, sketchy go-to-definition |
| type-checking speed (excluding transpilation) | benchmarks needed | benchmarks needed,in-depth description![]() |
| autocomplete |
|
|
| expressiveness | great (since TS @ 2.1) | great |
| type safety | very good (7 / 10) | great (8 / 10) |
specifying generic parameters during call-time (f<T>(x)) | yese.g. | yes (since Flow 0.72) |
| specifying generic parameters for type definitions | yes | yes |
| typings for public libraries | plenty of well maintained typings | a handful of mostly incomplete typings |
| unique features |
|
|
| type spread operator | shipped > 3.2rc | shipped >=0.42 |
| support for nullish coalescing proposal | no | yes |
| support for decorators proposal | yes, legacy proposal | only parsing of legacy proposal, no type-checking |
| support for extending built-in types | yes | no |
| userland plugins | basic, not effecting emitting yet (planned) | no |
| programmatic hooking | architecture prepared, work in progress | work in progress |
| documentation and resources |
|
|
| ease-of-understanding of errors | good | good in some, vague in other cases |
| transparency | meeting notes, leadership reasoning and roadmap happens mostly publicly | low transparency, roadmap developed behind closed doors |
| commercial support | no | no |
| nominal and structural typing | structural | mostlystructural, nominal forclasses andimported opaque type aliases |
| utility size (not emitted JavaScript) (latest version) |
functionfooGood<T:{x:number}>(obj: T): T{console.log(Math.abs(obj.x));returnobj;}
functionfooGood<Textends{x:number}>(obj:T):T{console.log(Math.abs(obj.x));returnobj;}
https://flow.org/blog/2015/03/12/Bounded-Polymorphism/
leta: ?string// equivalent to:leta:string|null|void
leta:string|null|undefined
Optional parametersimplicitly addundefined:
functionf(x?:number){}// is semantically the same as:functionf(x:number|undefined){}// and also same as (the `| undefined` is redundant):functionf(x?:number|undefined){}
Optional properties implicitly addundefined
classA{foo?:string;}
(1+1 :number);
(1+1)asnumber;// OR (old version, not recommended):<number>(1+1);
.flowconfig
[options]module.name_mapper='^\(.*\)\.css$' ->'<PROJECT_ROOT>/CSSModule.js.flow'
CSSModule.js.flow
//@flow// CSS modules have a `className` export which is a stringdeclareexportvarclassName:string;
declare module"*.css"{exportconstclassName:string;}
By default objects in Flow are not exact, i.e. they can contain more properties than declared, whereas in TypeScript they are always exact (must contain only declared properties). Infuture versions Flow plans to change this and make objects exact by default.
When using flow,{ name: string } only means “an object withat least a name property”.
typeExactUser={|name:string,age:number|};typeUser={name:string,age:number};typeOptionalUser=$Shape<User>;// all properties become optional
TypeScript is more strict here, in that if you want to use a property which is not declared, you must explicitly say so by defining the indexed property. It is possible to usedotted syntax to access indexed properties since TypeScript 2.2. This is mostly a design decision as it forces you to write the typings upfront.
typeExactUser={name:string,age:number};typeUser={name:string,age:number,[otherProperty:string]:any};typeOptionalUser=Partial<ExactUser>;// all properties become optional
importtype{UserID,User}from"./User.js";// equivalent:import{typeUserID,typeUser}from"./User.js";
TypeScript does not treat Types in any special way when importing.
import{UserID,User}from"./User.js";
Works the same in both cases, however Flow has an additional syntax to directly import atypeof:
importtypeof{jimiguitarasGuitarT}from"./User";// ORimport{typeofjimiguitar}from"./User.js";typeGuitarT=jimiguitar;// OR (below also works in TypeScript)import{jimiguitar}from"./User.js";typeGuitarT=typeofjimiguitar;
import{jimiguitar}from"./User";typeGuitarT=typeofjimiguitar;
When you don't know a type, commonly you would useany type. A restrictive type accepts anything, likeany but in order to use that variable you must ensure values type by refining it.
mixed
functionstringifyNum(num:number){// Do stuff}functionstringify(value:mixed){if(typeofvalue==='string'){return''+value;// Works!}if(typeofvalue==='number'){returnstringifyNum(value);// Works!}return'';}
Reference:https://flow.org/en/docs/types/mixed/
unknown
functionstringifyNum(num:number){// Do stuff}functionstringify(value:unknown){if(typeofvalue==='string'){return''+value;// Works!}if(typeofvalue==='number'){returnstringifyNum(value);// Works!}return'';}
Reference:https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#new-unknown-top-type
Classes are typed, so you don't need to define an explicit type for them.If you want to reference the type, you can do it the following way:
classTest{};typeTestType=typeofTest;constinstance=newTest();typeTestTypeFromInstance=Class<typeofinstance>;
classTest{};typeTestType=typeofTest;
varprops={foo:1,bar:'two',baz:'three',}typePropsType=typeofprops;typeKeysOfProps=$Enum<PropsType>;functiongetProp<T>(key: KeysOfProps): T{returnprops[key]}
varprops={foo:1,bar:'two',baz:'three',}typePropsType=typeofpropstypeKeysOfProps=keyofPropsType;functiongetProp<T>(key:KeysOfProps):T{returnprops[key]}
type$Record<T,U>={[key:$Enum<T>]:U}typeSomeRecord=$Record<{a:number},string>
typeSomeRecord=Record<{a:number},string>
typeA={thing:string}// when the property is a string constant use $PropertyType (i.e. you know it when typing)typelookedUpThing=$PropertyType<A,'thing'>// when you want the property to be dynamic use $ElementType (since Flow 0.49)functiongetProperty<T :Object,Key :string>(obj: T, key: Key): $ElementType<T,Key>{returnobj[key];}
Reference:
- facebook/flow#2952 (comment)
- https://github.com/facebook/flow/commit/968210c5887b5bdd47d17167300033d1e1077d1a
- facebook/flow#2464 (comment)
- flow/try
Arguably, it's a bit easier to type both cases in TS, since they follow the same pattern.
typeA={thing:string}typelookedUpThing=A['thing']// and...functiongetProperty<T,KextendskeyofT>(obj:T,key:K){returnobj[key];// Inferred type is T[K]}functionsetProperty<T,KextendskeyofT>(obj:T,key:K,value:T[K]){obj[key]=value;}
Reference:
Note: undocumented syntax, may change:
functionisNil(value:mixed):boolean%checks{returnvalue==null;}constthing=null;if(!isNil(thing)){constanother=thing.something;}
Reference:
Type-narrowing functions are called type guard functions in TypeScript.
functionisNil<T>(value:T|null):value isnull{returnvalue==null;}constthing:any=null;if(!isNil(thing)){constanother=thing.something;}
$Call utility type:
typeFn1=<T>(T) =>T;typeE=$Call<Fn1,number>;declarevar e:E;// E is number(42:E);// OK
Reference:https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4
ReturnType utility type:
typefn1<T>=(a:T)=>T;typeE=ReturnType<fn1<number>>;vare:E;// E is number
typeInputType={hello:string};typeMappedType=$ObjMap<InputType,()=>number>;
Reference:
- https://gist.github.com/gabro/bb83ed574690645053b815da2082b937
- https://twitter.com/andreypopp/status/782192355206135808
A bit more flexibility here, as you have access to each individual key name and can combine with Lookup types and even do simple transformations.
typeInputType={hello:string};typeMappedType={[PinkeyofInputType]:number;};
It is possible to declare multiple signatures for the same method (also called: overloading). This feature is undocumented, and only available in type declarations (.js.flow files or module statements), not inline/alongside your code.
declarefunctionadd(x:string,y:string):string;declarefunctionadd(x:number,y:number):number;declareclassAdder{add(x:string,y:string):string;add(x:number,y:number):number;}
However, it's possible to create function overloads inline for functions outside of classes, by using additional declarations.
declarefunctionadd(x:string,y:string):string;declarefunctionadd(x:number,y:number):number;functionadd(x,y){returnx+y;}add(1,1);// Okadd("1","1");// Okadd(1,"1");// Error
TypeScript supports both function and method overloading, in both: type definitions (.d.ts) and inline alongside code.
classAdder{add(x:string,y:string):string;add(x:number,y:number):number;add(x,y){returnx+y;}}functionadd(x:string,y:string):string;functionadd(x:number,y:number):number;functionadd(x,y){returnx+y;}
typeA={+b:string}leta:A={b:'something'}a.b='something-else';// ERROR
typeA={readonlyb:string}leta:A={b:'something'}a.b='something-else';// ERROR
One caveat that makes TypeScript'sreadonly less safe is that the samenon-readonly property in a type is compatible with areadonly property. This essentially means that you can pass an object withreadonly properties to a function which expects non-readonly properties and TypeScript willnot throw errors:example.
empty
functionreturnsImpossible(){thrownewError();}// type of returnsImpossible() is 'empty'
never
functionreturnsImpossible(){thrownewError();}// type of returnsImpossible() is 'never'
typeC=$Diff<{a:string,b:number},{a:string}>// C is { b: number}
Note however that $Diff is not an official feature.
It only works properly as lower bound, i.e. you can assign something to it, but can't use it after that.
(source)
You can define your own filter type, but it does not have a helper type for that.
classA{a:string;b:number;}classB{a:string;c:boolean;}typeOmit<T,U>=Pick<T,Exclude<keyofT,keyofU>>;//typeC=Omit<A,B>;// C is { b: number }
However, Flow implementation is stricter in this case, as B have a property that A does not have, it would rise an error. In Typescript, however, they would be ignored.
Most of the syntax of Flow and TypeScript is the same. TypeScript is more expressive for certain use-cases (advanced mapped types with keysof, readonly properties), and Flow is more expressive for others (e.g.$Diff).
The syntax in either tool is the same - question mark:? suffixing the parameter name:
function(a?:string){}
In TypeScript and Flow (since version 0.72) you may use specifythe type of a generic when calling the generic function or the constructor.
constset=newSet<string>();
Or using a more complex behavior:
functionmakeTgenerator<T>(){returnfunction(next:()=>T){constsomething=next();returnsomething;}}constusage=makeTgenerator<string>()// 'usage' is of type: (next: () => string) => string
functionsomething(this:{hello:string},firstArg:string){returnthis.hello+firstArg;}
classSomeClass{constructor(publicprop:string,privateprop2:string){// transpiles to:// this.prop = prop;// this.prop2 = prop2;}privateprop3:string;}
Add! to signify we know an object is non-null.
// Compiled with --strictNullChecksfunctionvalidateEntity(e?:Entity){// Throw exception if e is null or invalid entity}functionprocessEntity(e?:Entity){validateEntity(e);lets=e!.name;// Assert that e is non-null and access name}
typeXorY<T,U>=TextendsU ?X :Y;
This alone, introduces new helper types, or types aliases.
typeExclude<T,U>=TextendsU ?never :T;/** * Extract from T those types that are assignable to U */typeExtract<T,U>=TextendsU ?T :never;/** * Exclude null and undefined from T */typeNonNullable<T>=Textendsnull|undefined ?never :T;/** * Obtain the return type of a function type */typeReturnType<Textends(...args:any[])=>any>=Textends(...args:any[])=> inferR ?R :any;
You can use+ and- operators to modify mapped types.
typeMutable<T>={-readonly[PinkeyofT]:T[P]}interfaceFoo{readonlyabc:number;}// 'abc' is no longer read-only.typeTotallyMutableFoo=Mutable<Foo>
Required is a type mapper to make all properties of an object to be required.
Partial is a type mapper to make all properties of an object to be optional.
Readonly is a type mapper to make all properties of an object to be readonly.
* as a type or a generic parameter signifies to the type-checker to infer the type if possible
Array<*>
However this type was deprecated inFlow 0.72.
https://flow.org/en/docs/lang/variance/
functiongetLength(o:{+p: ?string}):number{returno.p ?o.p.length :0;}
Bivariance is amongthe design decisions driving TypeScript.
- microsoft/TypeScript#1265
- Undocumented Flow modifiersfacebook/flow#2464
- http://sitr.us/2015/05/31/advanced-features-in-flow.html
About
Differences between Flowtype and TypeScript -- syntax and usability
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
