typescript-vs-flowtype

Differences between Flowtype and TypeScript 2.1 -- syntax and usability

TypeScript vs Flow

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 2.1 -- specifically the syntax, usage and usability.

Disclaimer

This might be incomplete and/or contain mistakes. I'm open to contributions and comments.

Differences in usage and usability

TypeScript Flow
IDE integrations top-notch sketchy, must save file to run type-check; some IDEs have workarounds to run real-time
speed real-time good
autocomplete both during declaration and usage only for usage
expressiveness great (since TS @ 2.1) great
type safety very good (7 / 10) great (8 / 10)
specifying generic parameters during call-time yes [e.g.](http://www.typescriptlang.org/play/#src=function%20someFactory%3CT%3E()%20%7B%0D%0A%20%20return%20class%20%7B%0D%0A%20%20%20%20method(someParam%20%3A%20T)%20%7B%0D%0A%20%20%20%20%20%20%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0A%2F%2F%20how%20to%20invoke%20this%20factory%20with%20a%20defined%20%3CT%3E%3F%0D%0A%0D%0Aconst%20SomeClass%20%3D%20someFactory%3C%7B%20whatever%3A%20string%20%7D%3E()%0D%0Aconst%20someInstance%20%3D%20new%20SomeClass()%0D%0AsomeInstance.method('will-error-here')%0D%0A) no
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
  • autocomplete for object construction
  • declarable this in functions (typing someFunction.bind())
  • large library of typings
  • more flexible type mapping via iteration
  • namespacing
  • variance
  • existential types *
  • testing potential code-paths when types not declared for maximum inference
  • $Diff<A, B> type
type spread operator work in progress shipped >=0.42
ecosystem flexibility work in progress no extensions
programmatic hooking architecture prepared, work in progress work in progress
documentation and resources
  • very good docs
  • many books
  • videos
  • e-learning resources
incomplete, often vague docs
commercial support no no
error quality good good in some, vague in other cases

Differences in syntax

bounded polymorphism

Flow

function fooGood<T: { x: number }>(obj: T): T {
  console.log(Math.abs(obj.x));
  return obj;
}

TypeScript

function fooGood<T extends { x: number }>(obj: T): T {
  console.log(Math.abs(obj.x));
  return obj;
}

Reference

https://flowtype.org/blog/2015/03/12/Bounded-Polymorphism.html

maybe & nullable type

Flow

let a: ?string

// equvalent to:

let a: string | null | void

TypeScript

let a: string | null | undefined

Optional parameters implicitly add undefined:

function f(x?: number) { }
// same as:
function f(x?: number | undefined) { }

type casting

Flow

(1 + 1 : number);

TypeScript

(1 + 1) as number;

// OR (old version, not recommended):

<number> (1 + 1);

mapping dynamic module names

Flow

.flowconfig

[options]
module.name_mapper='^\(.*\)\.css$' -> '<PROJECT_ROOT>/CSSModule.js.flow'

CSSModule.js.flow

// @flow

// CSS modules have a `className` export which is a string
declare export var className: string;

TypeScript

declare module "*.css" {
  export const className: string;
}

Reference

Exact/Partial Object Types

By default objects in Flow are not exact (can contain more properties than declared), whereas in TypeScript they are always exact (must contain only declared properties).

Flow

When using flow, { name: string } only means “an object with at least a name property”.

type ExactUser = {| name: string, age: number |};
type User = { name: string, age: number };
type OptionalUser = $Shape<User>; // all properties become optional

TypeScript

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. You will be allowed to use not-explicitly defined properties but will have to access them through the bracket access syntax, i.e. UserInstance['someProperty']. At the moment, you cannot define "open" (non-exact) types using TypeScript. UPDATE: Possible to use dotted syntax since TypeScript 2.2. This is mostly a design decision as it forces you to write the typings upfront.

type ExactUser = { name: string, age: number };
type User = { name: string, age: number, [otherProperty: string]: any };
type OptionalUser = Partial<{ name: string, age: number }>; // all properties become optional

Reference

Importing types

Flow

import type {UserID, User} from "./User.js";

TypeScript

TypeScript does not treat Types in any special way when importing.

import {UserID, User} from "./User.js";

typeof

Works the same in both cases, however Flow has an additional syntax to directly import a typeof:

Flow

import typeof {jimiguitar as GuitarT} from "./User";

// OR (below also works in TypeScript)

import {jimiguitar} from "./User.js";
type GuitarT = typeof jimiguitar;

TypeScript

import {jimiguitar} from "./User";
type GuitarT = typeof jimiguitar;

Accessing the type of a Class

Flow

class Test {};
type TestType = Class<Test>;
// This should be equivalent to (if you can confirm, please send a PR):
type TestType = typeof Test;

TypeScript

class Test {};
type TestType = typeof Test;

Keys/Props Of Type

Flow

var props = {
  foo: 1,
  bar: 'two',
  baz: 'three',
}

type PropsType = typeof props;
type KeysOfProps = $Enum<PropsType>;

function getProp<T>(key: KeysOfProps): T {
  return props[key]
}

TypeScript

var props = {
  foo: 1,
  bar: 'two',
  baz: 'three',
}

type PropsType = typeof props
type KeysOfProps = keyof PropsType;

function getProp<T>(key: KeysOfProps): T {
  return props[key]
}

Records

Flow

type $Record<T, U> = {[key: $Enum<T>]: U}
type SomeRecord = $Record<{ a: number }, string>

TypeScript

type SomeRecord = Record<{ a: number }, string>

Lookup Types

Flow

type A = {
  thing: string
}

type lookedUpThing = $PropertyType<A, 'thing'>

TypeScript

type A = {
  thing: string
}

type lookedUpThing = A['thing']

Mapped Types / Foreach Property

Flow

type InputType = { hello: string };
type MappedType = $ObjMap<InputType, ()=>number>;

Reference:

TypeScript

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.

type InputType = { hello: string };
type MappedType = {
  [P in keyof InputType]: number;
};

Read-only Types

Flow

type A = {
  +b: string
}

let a: A = { b: 'something' }
a.b = 'something-else'; // ERROR

TypeScript

type A = {
  readonly b: string
}

let a: A = { b: 'something' }
a.b = 'something-else'; // ERROR

One caveat that makes TypeScript's readonly less safe is that the same non-readonly property in a type is compatible with a readonly property. This essentially means that you can pass an object with readonly properties to a function which expects non-readonly properties and TypeScript will not throw errors: example.

"Impossible flow" type

Flow

empty

function returnsImpossible() {
  throw new Error();
}

// type of returnsImpossible() is 'empty'

TypeScript

never

function returnsImpossible() {
  throw new Error();
}

// type of returnsImpossible() is 'never'

Same syntax

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).

optional parameters

Flow and TypeScript

function(a?: string) {}

TypeScript-only concepts

call-time generic parameters

In TypeScript, you can create more complex behaviors, like this:

function makeTgenerator<T>() {
  return function(next : () => T) {
    const something = next();
    return something;
  }
}

const usage = makeTgenerator<string>()
// 'usage' is of type: (next: () => string) => string

Flow

In Flow it is possible to define generic functions similarly to the above example, but only if one of the parameters or its return type is inferrable to the desired generic type, i.e. you cannot call any method/constructor using a custom T.

Declarable arbitrary this in functions (outside of objects)

function something(this: { hello: string }, firstArg: string) {
  return this.hello + firstArg;
}

Private and Public properties in classes

class SomeClass {
  constructor(public prop: string, private prop2: string) {
    // transpiles to:
    // this.prop = prop;
    // this.prop2 = prop2;
  }
  private prop3: string;
}

Non-null assertion operator

Add ! to signify we know an object is non-null.

// Compiled with --strictNullChecks
function validateEntity(e: Entity?) {
  // Throw exception if e is null or invalid entity
}

function processEntity(e: Entity?) {
  validateEntity(e);
  let s = e!.name;  // Assert that e is non-null and access name
}

Flow-only concepts

Difference types

type C = $Diff<{ a: string, b: number }, { a: string }>
// C is { b: number}

TypeScript has a proposal for an equivalent.

Inferred existential types

* as a type or a generic parameter signifies to the type-checker to infer the type if possible

Array<*>

TypeScript has a proposal for an equivalent (needs link).

Variance

https://flowtype.org/docs/variance.html

function getLength(o: {+p: ?string}): number {
  return o.p ? o.p.length : 0;
}

TypeScript proposal

Bivariance is among the design decisions driving TypeScript.

Flow's "mixed" type

The TypeScript equivalent of the mixed type is simply:

type mixed = {}

Reference: https://flowtype.org/docs/quick-reference.html#mixed

Useful References

Related Repositories

front-end-guide

front-end-guide

Study guide and introduction to the modern front end stack ...

All-About-Programming

All-About-Programming

Everything about programming!! ...

front-end-at-grab

front-end-at-grab

Study guide and introduction to mastering front end at Grab ...

awesome-libraries-resources

awesome-libraries-resources

Awesome js and css libraries for web development. ...

my-first-react-typescript

my-first-react-typescript

My first React app using TypeScript 2.1 ...