Typed JavaScript
Flow & TypeScript

Types In JavaScript

  • Values and expressions have types
typeof 'str'; // 'string'
const variable = 3;
typeof variable; // 'number'
let variable; // What will the type be? ¯\_(ツ)_/¯
  • Variables don't have types

Typed JavaScript

  • Variables can have types
// `value` is a 'number'
const value: number = 3;

// `add` takes two 'number' arguments
// and returns a number
function add(a: number, b: number): number {
  return a + b;
}

Competing tools

TypeScript & Flow

TypeScript

  • www.typescriptlang.org
  • By Microsoft. Used by Angular 2, Cycle.js, ...
  • Compiler: JavaScript superset to JavaScript

TypeScript

  • Adds: Types, Enums, public/private class methods, JSX...
  • Incompatible with Babel, and not extendable.
  • .ts extension: incompatible with some tools (AVA, ESLint)

TypeScript

Flow

Flow

  • Understands JavaScript with types syntax
  • Compatible with Babel, and thus extendable.
  • .js extension: compatible with some tools (AVA, ESLint)
  • Babel plugin to remove types from code

Flow

  • Types can be added incrementally to the project
  • Only files with `// @flow` will be linted
  • Not that many package definitions
  • flow-typed
  • Try it online: https://flowtype.org/try/

Which one could we use?

  • Flow!
  • Hard to move our codebase to TypeScript all at once

Type syntax

  • Basic type syntax is the same in both tools
  • We'll discover Flow's syntax, stricter than TypeScript's

Basic types

// number
const a: number = 3;

// string
const b: string = 'foo'

// boolean
const c: boolean = true;

// null
const d: null = null;

// void (undefined)
const e: void = undefined;

Types in expressions

// In functions
function foo(
  a: string = defaultValue,
  b?: number // optional parameter
): boolean {} // return type

// Objects
const object
    : {a: string, b: number}
    = {a: "foo", b: 0};

// Destructuring
const [a: number] = bar;
const {a: number = 2} = bar;

General base types

let bar: any;

const foo: any = bar;
foo.z()[0].bar.yolo + '!!!'; // No checks


function parseInt(value: mixed): string {
  if (typeof value === 'string') {
    return value;
  }
  if (typeof value === 'number') {
    return String(value);
  }
  return '0';
}
  • any: can be anything, and no checks will be made on it.

      Modules without types will be considered as type any)

  • mixed: can be anything, but checks will be needed to use value safely

Function types

function readFile(path: string, cb: Function): void {
}

// More precise
function readFile(
    path: string,
    cb: (err: Error, content: string) => void
): void {
}
  • Function: Takes any number of parameters, returns `any`

Objects

const obj: Object = {};
obj.foo; // considered as 'any'

// Loose (can have additional properties)
const obj
: { foo: string
  , bar?: number // bar is optional
  }
= { foo: 'yolo'
  , bar: 123
  , baz: true
  };

// Strict (can't have additional properties) using {| |}
const obja
: {| foo: string
  , bar?: number // bar is optional
  |}
= { foo: 'yolo'
  , bar: 123
  , baz: true // Not allowed
  };
  • Object: returns`any` for any property

Generic types

const numberArray: Array<number> = [1, 2, 3]; // Also available as number[]

const userP: Promise<Object> = getUser('Jeroen');

Type aliases

// user.js
type UserId = string;

export type User = {|
  id: UserId,
  name: string
|};

function getUser(id: UserId): Promise<User> {
  // ...
}


// greeting.js
import type {User} from './user';

function sayHiToUser(user: User): string {
  return `Hello ${user.name}`;
}

Union types

let PORT: string | number = '3000';
PORT = 3000;

type User =
  | AnonymousUser
  | RegisteredUser
;

type CardColor =
  | 'Heart'
  | 'Clover'
  | 'Diamond'
  | 'Spade'
;

type Card = {
  color: CardColor,
  number: number
};

const card: Card = {
  color: 'Spade',
  number: 1
};

Intersection types

type Foo = {a: string} & {b: string}

const foo: Foo = {a: 'foo', b: 'bar'};



import type {Request} from 'express';

type CoorpRequest =
  & Request
  & { logger: Function
    , currentUser: User
    }

Model data

type User = {|
  id: UserId,
  name: string,
  // ...
|};


type Discipline = {|
  ref: string,
  name: string,
  modules: Module[]
  // ...
|};

Prop types for components

type Props = {
  rating: ?number,
  maxRating: ?number,
  linkBuy: ?string,
  linkTry: ?string,
  author: {
    name: ?string,
    socialLinks: SocialLinks[]
  }
};

const DisciplineRightaside = (
  props: Props,
  // Tuple: array of size 0 (no children)
  children: []
) => {
}
const conditions = checker.shape({
  props: checker.shape({
    rating: checker.number.optional,
    maxRating: checker.number.optional,
    linkBuy: checker.string.optional,
    linkTry: checker.string.optional,
    author: checker.shape({
      name: checker.string.optional,
      socialLinks: checker.array.optional
    }).optional
  }),
  children: checker.none
});

Model data à la Elm

const initialModel: Model = 0;

const update = (
  model: Model = initialModel,
  action: Action
): Model => {
  switch (action.type) {
    case 'increment': {
      return model + 1;
    }
    case 'decrement': {
      return model - 1;
    }
    case 'reset': {
      return 0;
    }
  }
  return model;
};
type Model = number;

type Increment = {|
  type: 'increment'
|};
type Decrement = {|
  type: 'decrement'
|};
type Reset = {|
  type: 'reset'
|};

type Action =
  | Increment
  | Decrement
  | Reset
;

If you want to change your model, Flow will tell you where you missed stuff

Make impossible states impossible

type Survey = {
  questions: Array<string>,
  answers: Array<?string>
};

const survey: Survey = {
  questions: [
    "What is the color of Henry IV's white horse?"
  ],
  answers: [
    'White',
    'Barack Obama'
  ]
}

// WTF happened here?

Make impossible states impossible

type Question = {
  question: string,
  answer: ?string
};

type Survey = Array<Question>;

const survey: Survey = [
  {
    question: "What is the color of Henry IV's white horse?"
    answer: 'White'
  }
];

Types all the way down

  • Kris Jenkins - Types All The Way Down
  • Send type info to front-end
  • In our case: create a package with model typings and consume on the front-end

Add it to a project

  • `npm i -D flow-bin && touch .flowconfig`
  • npm test: `previous npm test && flow`
  • Add Babel, use babel-plugin-transform-flow-strip-types
  • With AVA: Add the same plugin to the babel
  • ESLint: Use `babel-eslint` parser
  • Example: api-platform

Dependency typings

  • flow-typed (beta)
  • Installs typings in a local folder
  • Creates `any` for all the exported functions of packages not found
  • Update, commit, contribute

Pros

  • Static type checker that detects errors
  • Safer refactoring
  • Code easier to read by typing the data
  • Model data types to avoid odd data states
  • For libraries, makes the client code that uses Flow much safer and easier to use

Cons

  • For libraries, makes client code that does not use Flow unsafer, as impossible arguments are not tested
  • Learning curve. Some things are hard to model.
  • Package definitions are rare, and often not up to date

Typed JavaScript

By Jeroen Engels

Typed JavaScript

  • 206
Loading comments...

More from Jeroen Engels