niieani/typescript-vs-flowtype

Differences between Flowtype and TypeScript -- syntax and usability

1,727

77 commits

updated Feb 29, 2020

See the code

README

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

Disclaimer

This document might be incomplete and/or contain mistakes and was last updated to describe TypeScript 3.7.0 and Flow 0.101.

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 and editing this page.

Thanks!

Differences in usage and usability

Some of these differences are subjective (e.g. error readability), and I'd love to make this as scientific as possible — so please contribute to make it better. :)

TypeScriptFlow
Leading Design Goal / North Staridentify errors in programs through a balance between correctness and productivityenforce type soundness / safety
IDE integrationstop-notch: language server, built-in refactorings, type and typedoc information on hover, snappy go-to-definitionlanguage 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 neededbenchmarks needed, in-depth description numberOfFiles · O( (LoCperFile + SizeOfTypesOfExports) ^ k )
autocomplete
  • both during declaration and usage
  • feels instantaneous
  • feels reliable
  • only for usage
  • feels sluggish (often a second or more of delay)
  • feels unreliable (sometimes does not show up at all)
expressivenessgreat (since TS @ 2.1)great
type safetyvery good (7 / 10)great (8 / 10)
specifying generic parameters during call-time (f<T>(x))yes e.g.yes (since Flow 0.72)
specifying generic parameters for type definitionsyesyes
typings for public librariesplenty of well maintained typingsa 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 * (deprecated since 0.72)
  • testing potential code-paths when types not declared for maximum inference
  • $Diff<A, B> type
type spread operatorno (planned)shipped >=0.42
support for nullish coalescing proposalshipped > 3.7betayes
support for decorators proposalyes, legacy proposalonly parsing of legacy proposal, no type-checking
support for extending built-in typesyesno
userland pluginsbasic, not effecting emitting yet (planned)no
programmatic hookingarchitecture prepared, work in progresswork in progress
documentation and resources
  • very good docs
  • many books
  • videos
  • e-learning resources
  • incomplete, often vague docs
    ease-of-understanding of errorsgoodgood in some, vague in other cases
    transparencymeeting notes, leadership reasoning and roadmap happens mostly publiclylow transparency, roadmap developed behind closed doors
    commercial supportnono
    nominal and structural typingstructural with plans to support nominalmostly structural, nominal for classes and imported opaque type aliases
    dynamic import typesimport('module-name') since 2.9undocumented $Exports<'module-name'>
    utility size (not emitted JavaScript) (latest version)typescript sizeflow-bin size

    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://flow.org/blog/2015/03/12/Bounded-Polymorphism/

    maybe & nullable type

    Flow

    let a: ?string
    
    // equivalent to:
    
    let a: string | null | void
    

    TypeScript

    let a: string | null | undefined
    

    Optional parameters implicitly add undefined:

    function f(x?: number) { }
    // is semantically the same as:
    function f(x: number | undefined) { }
    // and also same as (the `| undefined` is redundant):
    function f(x?: number | undefined) { }
    

    Optional properties implicitly add undefined

    class A {
      foo?: string;
    }
    

    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, i.e. they can contain more properties than declared, whereas in TypeScript they are always exact (must contain only declared properties). In future versions Flow plans to change this and make objects exact by default.

    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. It is possible to use dotted syntax to access indexed properties 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<ExactUser>; // all properties become optional
    

    Reference

    Importing types

    Flow

    import type {UserID, User} from "./User.js";
    // equivalent:
    import {type UserID, type 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
    
    import {typeof jimiguitar} from "./User.js";
    type GuitarT = jimiguitar;
    
    // OR (below also works in TypeScript)
    
    import {jimiguitar} from "./User.js";
    type GuitarT = typeof jimiguitar;
    

    TypeScript

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

    Restrictive type

    When you don't know a type, commonly you would use any type. A restrictive type accepts anything, like any but in order to use that variable you must ensure values type by refining it.

    Flow

    mixed

    function stringifyNum(num: number) {
      // Do stuff
    }
    
    function stringify(value: mixed) {
      if (typeof value === 'string') {
        return '' + value; // Works!
      }
      if (typeof value === 'number') {
        return stringifyNum(value); // Works!
      }
      return '';
    }
    

    Reference: https://flow.org/en/docs/types/mixed/

    Typescript

    unknown

    function stringifyNum(num: number) {
      // Do stuff
    }
    
    function stringify(value: unknown) {
      if (typeof value === 'string') {
        return '' + value; // Works!
      }
      if (typeof value === 'number') {
        return stringifyNum(value); // Works!
      }
      return '';
    }
    

    Reference: https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#new-unknown-top-type

    Accessing the type of a Class

    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:

    Flow

    class Test {};
    type TestType = typeof Test;
    
    const instance = new Test();
    type TestTypeFromInstance = Class<typeof instance>;
    

    TypeScript

    class Test {};
    type TestType = typeof Test;
    

    Nominal typing

    Flow treats classes as nominal types, whereas TypeScript treats them as structural types.

    Flow

    class Foo {};
    class Bar {};
    
    const foo: Foo = new Bar();
    // Cannot assign `new Bar()` to `foo` because `Bar` [1] is incompatible with `Foo` [2].
    

    TypeScript

    class Foo {};
    class Bar {};
    
    const foo: Foo = new Bar();
    // No errors!
    

    You can work around this with tricks like the following (declare only works in TypeScript >=3.7.0):

    class Foo {
        declare private __nominal: void;
    };
    class Bar {
        declare private __nominal: void;
    };
    
    const foo: Foo = new Bar();
    // Type 'Bar' is not assignable to type 'Foo'.
    // Types have separate declarations of a private property '__nominal'.(2322)
    

    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
    }
    
    // when the property is a string constant use $PropertyType (i.e. you know it when typing)
    type lookedUpThing = $PropertyType<A, 'thing'>
    
    // when you want the property to be dynamic use $ElementType (since Flow 0.49)
    function getProperty<T : Object, Key : string>(obj: T, key: Key): $ElementType<T, Key> {
        return obj[key];
    }
    

    Reference:

    TypeScript

    Arguably, it's a bit easier to type both cases in TS, since they follow the same pattern.

    type A = {
      thing: string
    }
    
    type lookedUpThing = A['thing']
    
    // and...
    
    function getProperty<T, K extends keyof T>(obj: T, key: K) {
        return obj[key];  // Inferred type is T[K]
    }
    
    function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
        obj[key] = value;
    }
    

    Reference:

    Type-narrowing functions

    These are functions that return a boolean, performing some logic to assert that a given input parameter is of a certain type.

    The implementations differ between Flow and TypeScript:

    In TypeScript, it ensures the mapping between: true and value is T, versus in the case of Flow, it ensures the value is "checked" against the logic within the body of the function (i.e. things like typeof, instanceof, value === undefined).

    This means you cannot tell Flow that the tested parameter is of an arbitrary type, which closes the door to complex cases, e.g.:

    • reusing logic from a different function
    • library definitions, where there is no body at all (it is possible to specify the body in the declaration, however you are still limited by the type of assertions you may specify)

    Flow

    function isNil(value: mixed): boolean %checks {
      return value == null;
    }
    
    const thing = null;
    
    if (!isNil(thing)) {
      const another = thing.something;
    }
    

    Reference:

    Caveats

    The current implementation in Flow is incomplete, which means you cannot yet use %checks in class methods.

    Example showing the limitation in the respective playgrounds: TypeScript vs Flow

    TypeScript

    Type-narrowing functions are called type guard functions in TypeScript.

    function isNil<T>(value: T | null): value is null {
      return value == null;
    }
    
    const thing: any = null;
    
    if (!isNil(thing)) {
      const another = thing.something;
    }
    

    Getting the type of a function call return value

    Flow

    $Call utility type:

    type Fn1 = <T>(T) => T;
    type E = $Call<Fn1, number>;
    
    declare var e: E; // E is number
    (42: E); // OK
    

    Reference: https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

    TypeScript

    ReturnType utility type:

    type fn1<T> = (a: T) => T;
    
    type E = ReturnType<fn1<number>>;
    
    var e: E; // E is number
    

    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;
    };
    

    Function and method overloading

    Flow

    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.

    declare function add(x: string, y: string): string;
    declare function add(x: number, y: number): number;
    
    declare class Adder {
      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.

    declare function add(x: string, y: string): string;
    declare function add(x: number, y: number): number;
    function add(x, y) {
      return x + y;
    }
    
    add(1, 1); // Ok
    add("1", "1"); // Ok
    add(1, "1"); // Error
    

    It is also possible to create function overloads using callable property syntax, see the section Object callable property.

    TypeScript

    TypeScript supports both function and method overloading, in both: type definitions (.d.ts) and inline alongside code.

    class Adder {
      add(x: string, y: string): string;
      add(x: number, y: number): number;
      add(x, y) {
        return x + y;
      }
    }
    
    
    function add(x: string, y: string): string;
    function add(x: number, y: number): number;
    function add(x, y) {
      return x + y;
    }
    

    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'
    

    Difference types

    Flow

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

    It only works properly as lower bound, i.e. you can assign something to it, but can't use it after that.

    (source)

    Flow also has $Rest<>, which represents the result of the JS object rest operator ({ ...rest }).

    type Props = { name: string, age: number };
    
    const props: Props = {name: 'Jon', age: 42};
    const {age, ...otherProps} = props;
    (otherProps: $Rest<Props, {|age: number|}>);
    otherProps.age;  // Error, since we removed it
    

    Typescript

    You can define your own filter type, but it does not have a helper type for that.

    class A {
      a: string;
      b: number;
    }
    
    class B {
      a: string;
      c: boolean;
    }
    
    type Omit<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
    //  
    
    type C = 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.

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

    Object callable property

    The basic syntax are the same, except Flow has special syntax for the internal call property slot.

    Both can be used to annotate function statics.

    Flow

    You can use objects with callable properties as functions: Try Flow

    type F = {
      (): string
    };
    const f: F = () => "hello";
    const hello: string = f();
    

    An overloaded function is a function with multiple call signatures. This is supported by Flow. And we list out the different syntaxes here: Try Flow

    type F = {
      (): string,
      [[call]]: (number) => string,
      [[call]](string): string
    }
    
    const f: F = (x?: number | string) => {
      return x ? x.toString() : '';
    }
    

    Use call property to annotate function statics: Try Flow

    type MemoizedFactorialType = {
      cache: {
        [number]: number,
      },
      [[call]](number): number,
    }
    
    const factorial: MemoizedFactorialType = n => {
      if (!factorial.cache) {
        factorial.cache = {}
      }
      if (factorial.cache[n] !== undefined) {
        return factorial.cache[n]
      }
      factorial.cache[n] = n === 0 ? 1 : n * factorial(n - 1)
      return factorial.cache[n]
    }
    

    Reference:

    TypeScript

    You can use objects with callable properties as functions: TypeScript Playground

    type F = {
      (): string;
    }
    const foo: F = () => "hello";
    const bar: string = foo();
    

    An overloaded function is a function with multiple call signatures. This is also supported by TypeScript: TypeScript Playground

    type F = {
      (): string,
      (x: number): string,
      (x: string): string
    }
    
    const f: F = (x?: number | string) => {
      return x ? x.toString() : '';
    }
    

    Use call property to annotate function statics: TypeScript Playground

    type MemoizedFactorialType = {
      cache?: {
        [n: number]: number,
      },
      (n: number): number,
    }
    
    const factorial: MemoizedFactorialType = n => {
      if (!factorial.cache) {
        factorial.cache = {}
      }
      else if (factorial.cache[n] !== undefined) {
        return factorial.cache[n]
      }
      factorial.cache[n] = n === 0 ? 1 : n * factorial(n - 1)
      return factorial.cache[n]
    }
    

    Reference:

    optional parameters

    Flow and TypeScript

    The syntax in either tool is the same - question mark: ? suffixing the parameter name:

    function(a?: string) {}
    

    call-time generic parameters

    In TypeScript and Flow (since version 0.72) you may use specify the type of a generic when calling the generic function or the constructor.

    const set = new Set<string>();
    

    Or using a more complex behavior:

    function makeTgenerator<T>() {
      return function(next: () => T) {
        const something = next();
        return something;
      }
    }
    
    const usage = makeTgenerator<string>()
    // 'usage' is of type: (next: () => string) => string
    

    Typing pure JS files (i.e. without transpilation)

    Flow

    Flow supports a comment-based syntax, by encapsulating type annotations in /* */-style comments:

    const f = (x /*: number */, y /*: number */) /*: number */ => x + y
    

    TypeScript

    TypeScript can check types with JavaScript files annotated with JSDoc comments:

    // JSDoc type syntax
    /** @type {function(number, number): number} */
    const f = (x, y) => x + y
    // equivalent TypeScript type syntax
    /** @type {(x: number, y: number) => number} */
    

    JSDoc's overloaded function comment syntax is not supported:

    /**
     * @param {string} input
     * @returns {string} result
     *//**
     * @param {number} input
     * @returns {string} result
     */
    function notSupported(input) { /* omit */ }
    

    However, we can express function overloading type in TypeScript's form in a tricky way:

    /** @type {{
                (): void;
                (code: 0): void;
                (code: 1, msg: string): void
              }} */
    const functionOverloads = (
      /** @type {0 | 1} */ code = 0,
      /** @type {string | undefined} */ msg = code === 0 ? undefined : ""
    ) => { /* omit */ }
    

    However, it still lacks some features:

    1. There is no way to pass type parameter when invoking generic functions.
    2. TypeScript cannot parse conditional types in JSDoc comments correctly. #27424
    3. There is no equivalent form of as const assertion. #30445

    TypeScript-only concepts

    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
    }
    

    Conditional Typing

    type XorY<T, U> = T extends U ? X : Y;
    

    This alone, introduces new helper types, or types aliases.

    type Exclude<T, U> = T extends U ? never : T;
    
    /**
     * Extract from T those types that are assignable to U
     */
    type Extract<T, U> = T extends U ? T : never;
    
    /**
     * Exclude null and undefined from T
     */
    type NonNullable<T> = T extends null | undefined ? never : T;
    
    /**
     * Obtain the return type of a function type
     */
    type ReturnType<T extends (...args: any[]) => any> =
        T extends (...args: any[]) => infer R ? R : any;
    

    Mapped Type Modifiers

    You can use + and - operators to modify mapped types.

    type Mutable<T> = {
      -readonly [P in keyof T]: T[P]
    }
    
    interface Foo {
      readonly abc: number;
    }
    
    // 'abc' is no longer read-only.
    type TotallyMutableFoo = Mutable<Foo>
    

    Helper type modifiers

    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.

    Flow-only concepts

    Inferred existential types

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

    Array<*>
    

    However this type was deprecated in Flow 0.72.

    TypeScript proposal

    Variance

    https://flow.org/en/docs/lang/variance/

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

    TypeScript proposal

    Bivariance is among the design decisions driving TypeScript.

    Opaque Type Alias

    https://flow.org/en/docs/types/opaque-types/

    opaque type Alias = Type;
    opaque type Alias: SuperType = Type; // with subtyping constrains
    

    Within the same file the opaque type alias is defined, opaque type aliases behave exactly as type aliases.

    Outside the defining file, i.e. when importing an opaque type alias, it behaves like a nominal type. If the opaque type alias is defined with subtyping constrains, it can be used as the super type when outside the defining file.

    export opaque type Age: number = number;
    
    function newAge(age: number): Age {
        return age; // ok within same file, not ok outside defining file
    }
    
    function incAge(age: Age): number {
        return age + 1; // ok
    }
    

    TypeScript dose not have opaque type, but we can define an utility type with intersection type to mimic the behavior of Flow's opaque type alias with subtyping constrains used outside the defining file.

    type Opaque<T, U> = U & { readonly __TYPE__: T }
    type Age = Opaque<'age', number>
    
    function newAge(age: number): Age {
        return age; // not ok
    }
    
    function incAge(age: Age): number {
        return age + 1; // ok
    }
    

    Object type spread

    Object type spread acts as object spread but for types. Unlike intersection types type spreads work with exact object types and overwrite existing properties.

    type Foo = {| foo: string, bar: string |}
    type Bar = {| bar: number |}
    
    type FooBarIntersection = Foo & Bar
    type FooBarSpread = {| ...Foo, ...Bar |}
    
    const fooBarInterect: FooBarIntersection = { foo: '123', bar: 12 } // not ok
    const fooBarString: FooBarSpread = { foo: '123', bar: 'string' } // not ok
    const fooBar: FooBarSpread = { foo: '123', bar: 12 } // ok
    

    While TypeScript does understand object spread, the support for object type spread is not implemented.

    Useful References

    comparison
    documentation
    flow
    flowtype
    hacktoberfest
    javascript-tools
    reference
    typescript
    typing

    niieani/typescript-vs-flowtype

    Differences between Flowtype and TypeScript -- syntax and usability

    1,727

    77 commits

    updated Feb 29, 2020

    See the code

    README

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

    Disclaimer

    This document might be incomplete and/or contain mistakes and was last updated to describe TypeScript 3.7.0 and Flow 0.101.

    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 and editing this page.

    Thanks!

    Differences in usage and usability

    Some of these differences are subjective (e.g. error readability), and I'd love to make this as scientific as possible — so please contribute to make it better. :)

    TypeScriptFlow
    Leading Design Goal / North Staridentify errors in programs through a balance between correctness and productivityenforce type soundness / safety
    IDE integrationstop-notch: language server, built-in refactorings, type and typedoc information on hover, snappy go-to-definitionlanguage 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 neededbenchmarks needed, in-depth description numberOfFiles · O( (LoCperFile + SizeOfTypesOfExports) ^ k )
    autocomplete
    • both during declaration and usage
    • feels instantaneous
    • feels reliable
    • only for usage
    • feels sluggish (often a second or more of delay)
    • feels unreliable (sometimes does not show up at all)
    expressivenessgreat (since TS @ 2.1)great
    type safetyvery good (7 / 10)great (8 / 10)
    specifying generic parameters during call-time (f<T>(x))yes e.g.yes (since Flow 0.72)
    specifying generic parameters for type definitionsyesyes
    typings for public librariesplenty of well maintained typingsa 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 * (deprecated since 0.72)
    • testing potential code-paths when types not declared for maximum inference
    • $Diff<A, B> type
    type spread operatorno (planned)shipped >=0.42
    support for nullish coalescing proposalshipped > 3.7betayes
    support for decorators proposalyes, legacy proposalonly parsing of legacy proposal, no type-checking
    support for extending built-in typesyesno
    userland pluginsbasic, not effecting emitting yet (planned)no
    programmatic hookingarchitecture prepared, work in progresswork in progress
    documentation and resources
    • very good docs
    • many books
    • videos
    • e-learning resources
    • incomplete, often vague docs
      ease-of-understanding of errorsgoodgood in some, vague in other cases
      transparencymeeting notes, leadership reasoning and roadmap happens mostly publiclylow transparency, roadmap developed behind closed doors
      commercial supportnono
      nominal and structural typingstructural with plans to support nominalmostly structural, nominal for classes and imported opaque type aliases
      dynamic import typesimport('module-name') since 2.9undocumented $Exports<'module-name'>
      utility size (not emitted JavaScript) (latest version)typescript sizeflow-bin size

      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://flow.org/blog/2015/03/12/Bounded-Polymorphism/

      maybe & nullable type

      Flow

      let a: ?string
      
      // equivalent to:
      
      let a: string | null | void
      

      TypeScript

      let a: string | null | undefined
      

      Optional parameters implicitly add undefined:

      function f(x?: number) { }
      // is semantically the same as:
      function f(x: number | undefined) { }
      // and also same as (the `| undefined` is redundant):
      function f(x?: number | undefined) { }
      

      Optional properties implicitly add undefined

      class A {
        foo?: string;
      }
      

      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, i.e. they can contain more properties than declared, whereas in TypeScript they are always exact (must contain only declared properties). In future versions Flow plans to change this and make objects exact by default.

      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. It is possible to use dotted syntax to access indexed properties 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<ExactUser>; // all properties become optional
      

      Reference

      Importing types

      Flow

      import type {UserID, User} from "./User.js";
      // equivalent:
      import {type UserID, type 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
      
      import {typeof jimiguitar} from "./User.js";
      type GuitarT = jimiguitar;
      
      // OR (below also works in TypeScript)
      
      import {jimiguitar} from "./User.js";
      type GuitarT = typeof jimiguitar;
      

      TypeScript

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

      Restrictive type

      When you don't know a type, commonly you would use any type. A restrictive type accepts anything, like any but in order to use that variable you must ensure values type by refining it.

      Flow

      mixed

      function stringifyNum(num: number) {
        // Do stuff
      }
      
      function stringify(value: mixed) {
        if (typeof value === 'string') {
          return '' + value; // Works!
        }
        if (typeof value === 'number') {
          return stringifyNum(value); // Works!
        }
        return '';
      }
      

      Reference: https://flow.org/en/docs/types/mixed/

      Typescript

      unknown

      function stringifyNum(num: number) {
        // Do stuff
      }
      
      function stringify(value: unknown) {
        if (typeof value === 'string') {
          return '' + value; // Works!
        }
        if (typeof value === 'number') {
          return stringifyNum(value); // Works!
        }
        return '';
      }
      

      Reference: https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#new-unknown-top-type

      Accessing the type of a Class

      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:

      Flow

      class Test {};
      type TestType = typeof Test;
      
      const instance = new Test();
      type TestTypeFromInstance = Class<typeof instance>;
      

      TypeScript

      class Test {};
      type TestType = typeof Test;
      

      Nominal typing

      Flow treats classes as nominal types, whereas TypeScript treats them as structural types.

      Flow

      class Foo {};
      class Bar {};
      
      const foo: Foo = new Bar();
      // Cannot assign `new Bar()` to `foo` because `Bar` [1] is incompatible with `Foo` [2].
      

      TypeScript

      class Foo {};
      class Bar {};
      
      const foo: Foo = new Bar();
      // No errors!
      

      You can work around this with tricks like the following (declare only works in TypeScript >=3.7.0):

      class Foo {
          declare private __nominal: void;
      };
      class Bar {
          declare private __nominal: void;
      };
      
      const foo: Foo = new Bar();
      // Type 'Bar' is not assignable to type 'Foo'.
      // Types have separate declarations of a private property '__nominal'.(2322)
      

      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
      }
      
      // when the property is a string constant use $PropertyType (i.e. you know it when typing)
      type lookedUpThing = $PropertyType<A, 'thing'>
      
      // when you want the property to be dynamic use $ElementType (since Flow 0.49)
      function getProperty<T : Object, Key : string>(obj: T, key: Key): $ElementType<T, Key> {
          return obj[key];
      }
      

      Reference:

      TypeScript

      Arguably, it's a bit easier to type both cases in TS, since they follow the same pattern.

      type A = {
        thing: string
      }
      
      type lookedUpThing = A['thing']
      
      // and...
      
      function getProperty<T, K extends keyof T>(obj: T, key: K) {
          return obj[key];  // Inferred type is T[K]
      }
      
      function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
          obj[key] = value;
      }
      

      Reference:

      Type-narrowing functions

      These are functions that return a boolean, performing some logic to assert that a given input parameter is of a certain type.

      The implementations differ between Flow and TypeScript:

      In TypeScript, it ensures the mapping between: true and value is T, versus in the case of Flow, it ensures the value is "checked" against the logic within the body of the function (i.e. things like typeof, instanceof, value === undefined).

      This means you cannot tell Flow that the tested parameter is of an arbitrary type, which closes the door to complex cases, e.g.:

      • reusing logic from a different function
      • library definitions, where there is no body at all (it is possible to specify the body in the declaration, however you are still limited by the type of assertions you may specify)

      Flow

      function isNil(value: mixed): boolean %checks {
        return value == null;
      }
      
      const thing = null;
      
      if (!isNil(thing)) {
        const another = thing.something;
      }
      

      Reference:

      Caveats

      The current implementation in Flow is incomplete, which means you cannot yet use %checks in class methods.

      Example showing the limitation in the respective playgrounds: TypeScript vs Flow

      TypeScript

      Type-narrowing functions are called type guard functions in TypeScript.

      function isNil<T>(value: T | null): value is null {
        return value == null;
      }
      
      const thing: any = null;
      
      if (!isNil(thing)) {
        const another = thing.something;
      }
      

      Getting the type of a function call return value

      Flow

      $Call utility type:

      type Fn1 = <T>(T) => T;
      type E = $Call<Fn1, number>;
      
      declare var e: E; // E is number
      (42: E); // OK
      

      Reference: https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

      TypeScript

      ReturnType utility type:

      type fn1<T> = (a: T) => T;
      
      type E = ReturnType<fn1<number>>;
      
      var e: E; // E is number
      

      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;
      };
      

      Function and method overloading

      Flow

      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.

      declare function add(x: string, y: string): string;
      declare function add(x: number, y: number): number;
      
      declare class Adder {
        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.

      declare function add(x: string, y: string): string;
      declare function add(x: number, y: number): number;
      function add(x, y) {
        return x + y;
      }
      
      add(1, 1); // Ok
      add("1", "1"); // Ok
      add(1, "1"); // Error
      

      It is also possible to create function overloads using callable property syntax, see the section Object callable property.

      TypeScript

      TypeScript supports both function and method overloading, in both: type definitions (.d.ts) and inline alongside code.

      class Adder {
        add(x: string, y: string): string;
        add(x: number, y: number): number;
        add(x, y) {
          return x + y;
        }
      }
      
      
      function add(x: string, y: string): string;
      function add(x: number, y: number): number;
      function add(x, y) {
        return x + y;
      }
      

      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'
      

      Difference types

      Flow

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

      It only works properly as lower bound, i.e. you can assign something to it, but can't use it after that.

      (source)

      Flow also has $Rest<>, which represents the result of the JS object rest operator ({ ...rest }).

      type Props = { name: string, age: number };
      
      const props: Props = {name: 'Jon', age: 42};
      const {age, ...otherProps} = props;
      (otherProps: $Rest<Props, {|age: number|}>);
      otherProps.age;  // Error, since we removed it
      

      Typescript

      You can define your own filter type, but it does not have a helper type for that.

      class A {
        a: string;
        b: number;
      }
      
      class B {
        a: string;
        c: boolean;
      }
      
      type Omit<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
      //  
      
      type C = 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.

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

      Object callable property

      The basic syntax are the same, except Flow has special syntax for the internal call property slot.

      Both can be used to annotate function statics.

      Flow

      You can use objects with callable properties as functions: Try Flow

      type F = {
        (): string
      };
      const f: F = () => "hello";
      const hello: string = f();
      

      An overloaded function is a function with multiple call signatures. This is supported by Flow. And we list out the different syntaxes here: Try Flow

      type F = {
        (): string,
        [[call]]: (number) => string,
        [[call]](string): string
      }
      
      const f: F = (x?: number | string) => {
        return x ? x.toString() : '';
      }
      

      Use call property to annotate function statics: Try Flow

      type MemoizedFactorialType = {
        cache: {
          [number]: number,
        },
        [[call]](number): number,
      }
      
      const factorial: MemoizedFactorialType = n => {
        if (!factorial.cache) {
          factorial.cache = {}
        }
        if (factorial.cache[n] !== undefined) {
          return factorial.cache[n]
        }
        factorial.cache[n] = n === 0 ? 1 : n * factorial(n - 1)
        return factorial.cache[n]
      }
      

      Reference:

      TypeScript

      You can use objects with callable properties as functions: TypeScript Playground

      type F = {
        (): string;
      }
      const foo: F = () => "hello";
      const bar: string = foo();
      

      An overloaded function is a function with multiple call signatures. This is also supported by TypeScript: TypeScript Playground

      type F = {
        (): string,
        (x: number): string,
        (x: string): string
      }
      
      const f: F = (x?: number | string) => {
        return x ? x.toString() : '';
      }
      

      Use call property to annotate function statics: TypeScript Playground

      type MemoizedFactorialType = {
        cache?: {
          [n: number]: number,
        },
        (n: number): number,
      }
      
      const factorial: MemoizedFactorialType = n => {
        if (!factorial.cache) {
          factorial.cache = {}
        }
        else if (factorial.cache[n] !== undefined) {
          return factorial.cache[n]
        }
        factorial.cache[n] = n === 0 ? 1 : n * factorial(n - 1)
        return factorial.cache[n]
      }
      

      Reference:

      optional parameters

      Flow and TypeScript

      The syntax in either tool is the same - question mark: ? suffixing the parameter name:

      function(a?: string) {}
      

      call-time generic parameters

      In TypeScript and Flow (since version 0.72) you may use specify the type of a generic when calling the generic function or the constructor.

      const set = new Set<string>();
      

      Or using a more complex behavior:

      function makeTgenerator<T>() {
        return function(next: () => T) {
          const something = next();
          return something;
        }
      }
      
      const usage = makeTgenerator<string>()
      // 'usage' is of type: (next: () => string) => string
      

      Typing pure JS files (i.e. without transpilation)

      Flow

      Flow supports a comment-based syntax, by encapsulating type annotations in /* */-style comments:

      const f = (x /*: number */, y /*: number */) /*: number */ => x + y
      

      TypeScript

      TypeScript can check types with JavaScript files annotated with JSDoc comments:

      // JSDoc type syntax
      /** @type {function(number, number): number} */
      const f = (x, y) => x + y
      // equivalent TypeScript type syntax
      /** @type {(x: number, y: number) => number} */
      

      JSDoc's overloaded function comment syntax is not supported:

      /**
       * @param {string} input
       * @returns {string} result
       *//**
       * @param {number} input
       * @returns {string} result
       */
      function notSupported(input) { /* omit */ }
      

      However, we can express function overloading type in TypeScript's form in a tricky way:

      /** @type {{
                  (): void;
                  (code: 0): void;
                  (code: 1, msg: string): void
                }} */
      const functionOverloads = (
        /** @type {0 | 1} */ code = 0,
        /** @type {string | undefined} */ msg = code === 0 ? undefined : ""
      ) => { /* omit */ }
      

      However, it still lacks some features:

      1. There is no way to pass type parameter when invoking generic functions.
      2. TypeScript cannot parse conditional types in JSDoc comments correctly. #27424
      3. There is no equivalent form of as const assertion. #30445

      TypeScript-only concepts

      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
      }
      

      Conditional Typing

      type XorY<T, U> = T extends U ? X : Y;
      

      This alone, introduces new helper types, or types aliases.

      type Exclude<T, U> = T extends U ? never : T;
      
      /**
       * Extract from T those types that are assignable to U
       */
      type Extract<T, U> = T extends U ? T : never;
      
      /**
       * Exclude null and undefined from T
       */
      type NonNullable<T> = T extends null | undefined ? never : T;
      
      /**
       * Obtain the return type of a function type
       */
      type ReturnType<T extends (...args: any[]) => any> =
          T extends (...args: any[]) => infer R ? R : any;
      

      Mapped Type Modifiers

      You can use + and - operators to modify mapped types.

      type Mutable<T> = {
        -readonly [P in keyof T]: T[P]
      }
      
      interface Foo {
        readonly abc: number;
      }
      
      // 'abc' is no longer read-only.
      type TotallyMutableFoo = Mutable<Foo>
      

      Helper type modifiers

      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.

      Flow-only concepts

      Inferred existential types

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

      Array<*>
      

      However this type was deprecated in Flow 0.72.

      TypeScript proposal

      Variance

      https://flow.org/en/docs/lang/variance/

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

      TypeScript proposal

      Bivariance is among the design decisions driving TypeScript.

      Opaque Type Alias

      https://flow.org/en/docs/types/opaque-types/

      opaque type Alias = Type;
      opaque type Alias: SuperType = Type; // with subtyping constrains
      

      Within the same file the opaque type alias is defined, opaque type aliases behave exactly as type aliases.

      Outside the defining file, i.e. when importing an opaque type alias, it behaves like a nominal type. If the opaque type alias is defined with subtyping constrains, it can be used as the super type when outside the defining file.

      export opaque type Age: number = number;
      
      function newAge(age: number): Age {
          return age; // ok within same file, not ok outside defining file
      }
      
      function incAge(age: Age): number {
          return age + 1; // ok
      }
      

      TypeScript dose not have opaque type, but we can define an utility type with intersection type to mimic the behavior of Flow's opaque type alias with subtyping constrains used outside the defining file.

      type Opaque<T, U> = U & { readonly __TYPE__: T }
      type Age = Opaque<'age', number>
      
      function newAge(age: number): Age {
          return age; // not ok
      }
      
      function incAge(age: Age): number {
          return age + 1; // ok
      }
      

      Object type spread

      Object type spread acts as object spread but for types. Unlike intersection types type spreads work with exact object types and overwrite existing properties.

      type Foo = {| foo: string, bar: string |}
      type Bar = {| bar: number |}
      
      type FooBarIntersection = Foo & Bar
      type FooBarSpread = {| ...Foo, ...Bar |}
      
      const fooBarInterect: FooBarIntersection = { foo: '123', bar: 12 } // not ok
      const fooBarString: FooBarSpread = { foo: '123', bar: 'string' } // not ok
      const fooBar: FooBarSpread = { foo: '123', bar: 12 } // ok
      

      While TypeScript does understand object spread, the support for object type spread is not implemented.

      Useful References

      comparison
      documentation
      flow
      flowtype
      hacktoberfest
      javascript-tools
      reference
      typescript
      typing

      Significant stargazers

      (top 24 of 32)

      Orta Therox

      6,294 followers · starred Dec 2017

      Simon Lydell

      576 followers · starred Oct 2018

      Alexey Pyltsyn

      1,272 followers · starred Sep 2017

      Steve Mao

      1,046 followers · starred Jul 2017