# TypeScript

Développé par Microsoft

C'est un sur-ensemble de JavaScript (c'est-à-dire que tout code JavaScript correct peut être utilisé avec TypeScript). Le code TypeScript est transcompilé en JavaScript, pouvant ainsi être interprété par n'importe quel navigateur web ou moteur JavaScript.

# auteurs / contributeurs

Anders Hejlsberg (fr) (GitHub profile)

Author of Turbo Pascal, chief architect of Delphi, lead architect of C#, conceptor of .NET framework and core developper and conceptor of TypeScript.

Daniel Rosenwasser

Program Manager of TypeScript

Ryan Cavanaugh

TypeScript lead dev

Mohamed Hegazy

TypeScript dev (retired)

# articles

TypeScript Evolution - Marius Schulz

34 articles about TS features from TS@2.0 to TS@2.8

TypeScript lead dev Ryan Cavanaugh about TypeScript and OOP :

"I'm using TypeScript so I have to write OOP code with classes" :: "I got a paintbrush from Home Depot so I have to paint my house orange"

# adoption

@swyx tweet about TS migration

React ecosystem libraries that have rewritten their core to @TypeScript (not just offering TypeScript support):

  • Next.js
  • @ReactNative CLI
  • React Router
  • @expo
  • Redux
  • @Yarnpkg
  • @fbjest
  • @storybookjs
  • @apollographql
  • @Gatsbyjs

# documentation

# Difference between script as a file and script at runtime

Taken from this article from Charly Poly

TS compile time vs runtime

# starters projects

samples - www.typescriptlang.org

TypeScript Node Starter - github.com/Microsoft

See in particular their Type Definition (.d.ts) Files management which give a lot of infos.

# tsconfig.json

tsconfig.json official doc - www.typescriptlang.org

tsconfig.json schema on json.schemastore.org

TypeScript Configuration - angular.io/guide

# compiler options

# --noImplicitAny

See noImplicitAny in TypeScript Deep Dive from basarat.gitbooks.io

By default this option is set to false.

When a type is not defined by the programmer and when it is impossible to infer it, TypeScript infers an any type, example :


function log(someArg) {
  sendDataToServer(someArg);
}

// What arg is valid and what isn't?
log(123);
log('hello world');

1
2
3
4
5
6
7
8
9

When set to true, the compiler raises an error in that case.

It forces the programmer to explicitly set the type and at least the any type which in fact remove type checking (it allows anything).

# --strictNullChecks

See Nullable Types section in Advanced Types from TS official handbook

See Should I use the strictNullChecks TypeScript compiler flag - yes or no? - www.tsmean.com/articles

Introduced in TS 2.0.

By default this option is set to false for retro compat purposes.

In JavaScript and without this option in TypeScript, undefined and null are assignable to anything. We can do this :


let toto = 'toto';
toto = null;

1
2
3
4

Or in TypeScript :


let toto: string = 'toto';
toto = null;

1
2
3
4

With this is option set to true, it is not possible anymore.

The intent is to avoid null pointers exceptions.

If the option is set, the programmer needs to explicitly declare when a value can be undefined or null like this :


let toto: string | null = 'toto';
toto = null;

toto = undefined; // Still raise error, should have declare toto as 'let toto: string | null | undefined;'

1
2
3
4
5
6
# optional parameters

If the option is set and an optional parameter is used, the type is implicitly considered to be an union with undefined :


function f(x: number, y?: number) {
    return x + (y || 0);
}
f(1, 2); // OK
f(1); // OK
f(1, undefined); // OK
f(1, null); // error, 'null' is not assignable to 'number | undefined'

1
2
3
4
5
6
7
8
9

# declaration files

Introduction - www.typescriptlang.org/docs/handbook

Consumption of *.d.ts files - www.typescriptlang.org/docs/handbook

install a @types/<lib> package or if the lib itself include a "types" prop in its package.json it's already there.

See DefinitelyTyped @ github.com for @types consumption and TS publishing doc for "types" prop.

Ex : Node.js typings are there.

Ambient Declarations - basarat.gitbooks.io/typescript

How do you produce a .d.ts “typings” definition file from an existing JavaScript library? - stackoverflow.com - 20121019

Getting started with TypeScript type definitions - medium.com/@jonjam - 20171113

How to create your own TypeScript type definition files (.d.ts) and contribute to DefinitelyTyped on GitHub - blog.wolksoftware.com - 20161015

# tools

dts-gen - github.com/Microsoft

# best practices

# managing export / import

export default considered harmful

Barrel

practice of creating an index.ts file inside a directory to re-export files (imply to avoid export default) allows destructured import : import { Foo, Bar, Baz } from '../demo'; // demo/index.ts is implied

# Linters

# TSLint

tslint - palantir.github.io

TSLint command-line interface

TSLint rules list

# TSLint deprecated soon

TSLint in 2019 - medium.com/palantir - 20190219

TSLint will be deprecated asap in favor of typescript-eslint.

# typescript-eslint

https://github.com/typescript-eslint

typescript-eslint/typescript-eslint - github.com

Monorepo for all the tooling which enables ESLint to support TypeScript https://typescript-eslint.io/

# videos

Migration progressive (passer les .js en .ts suffit à migrer sur typescript et inversement pour rollback)

Facilité apprentissage (juste le fait de typer)

Compatible ES6 (ajoute des polyfills, 90% de babel, donc autant faire ES5 -> typescript plutôt que ES5 -> ES6 -> TS

Refactoring simplifié du fait des types (erreurs à la compilation)

Assitance IDE meilleure (Webstorm nickel)

# erreurs tscompiler

errors codes classification :

Diagnostics are categorized into general ranges. If adding a new diagnostic message, use the first integral number greater than the last used number in the appropriate range.

1000 range for syntactic messages 2000 for semantic messages 4000 for declaration emit messages 5000 for compiler options messages 6000 for command line compiler messages 7000 for noImplicitAny messages

# Erreurs courantes après migration ES5/ES6 vers TS

  • TS2339: Property 'xxx' does not exist on type 'Yyyy'.

Cf issue 6373 : Getting error TS2339: Property does not exist on type for a valid ES6 class

Cf issue 2606 : ES6 should be valid TypeScript

class Car {
    constructor(weight) {
        this.weight = weight;
    }
}
1
2
3
4
5

is invalid ts code, it will output Error:(3, 14) TS2339: Property 'weight' does not exist on type 'Car'.

class Car {
    weight: number;

    constructor(weight: number) {
        this.weight = weight;
    }
}
1
2
3
4
5
6
7

is required by TS.

Anyway it is a non-blocker to generate the target JavaScript bundle.

If the input code is syntactically correct (prior to type checking) then it can generate ES output, and it is "valid" TS. At this first level, TS is a superset of ES, in that the set of valid TS programs is larger than the set of valid ES programs (because it includes all the valid ES programs plus those with type annotations).

The second level is type-correctness, which is what your error is complaining about. At this level, TS can act as a subset of ES: some valid ES programs, such as your example, are not type-correct TS programs.

  • error TS2380: 'get' and 'set' accessor must have the same type.

See TypeScript/issues/4087 and TypeScript/issues/2521

A classical pattern in JS with class to define a model is :

class MyClass {
    constructor(value) {
        this._myDate = value;
    }

    get myDate() {
        return this._myDate;
    }

    set myDate(value) {
        this._myDate = moment(value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

The accessors are used to refine / format the data set to the instance.

But in TypeScript accessors must have the same type, and when converted to TS the compiler raise an error :

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

The workaround is to create a dedicated function but we loose the instance.myDate = new Date(); usage.

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Another workaround would be to type _myDate with Date | moment.Moment but we loose a lot of type checking here.

See cons TS2380 rule arguments :

juanpablodelatorre

When TypeScript limits JavaScript, it becomes more of a nuisance than an advantage. Isn't TypeScript meant to help developers communicate with each other?

Also, setters are called Mutators for a reason. If I wouldn't need any kind of conversion, I wouldn't use a setter, I would set the variable by myself.

gmurray81

impact with Angular and components inputs from templates.

See pros TS2380 rule arguments :

kitsonk (TypeScript contributor)

IMO, ever since JavaScript allowed accessors, people have potentially created confusing APIs with them. I personally find it confusing that something on assignment magically changes to something else. Implicit anything, especially type conversion, is the bane of JavaScript IMO. It is exactly the flexibility that causes problems.

mhegazy (TypeScript contributor)

After thinking about this some more. i think the issue here is really the complexity of the implementation.

He flagged issue as "Too Complex" and "Design Limitation" labels after that post then closed the issue 2521.

kitsonk (TypeScript contributor)

The labels on the issue indicate it is a design limitation and the implementation would be considered too complex, which essentially means that if someone has a super compelling reason why this should be the case, it is not going anywhere.

# features

# Control Flow Based Type Analysis

See what's new in TS@2.0 - www.typescriptlang.org

TypeScript 2.0: Control Flow Based Type Analysis - mariusschulz.com/blog - 20160930

With TypeScript 2.0, the type checker analyses all possible flows of control in statements and expressions to produce the most specific type possible (the narrowed type) at any given location for a local variable or parameter that is declared to have a union type.

# conditional types

Added in TypeScript 2.8 see release notes.

Super nice answer here

Conditional types probably aren't something you'll write every day, but you might end up using them indirectly all the time. That's because they're great for 'plumbing' or 'framework' code, for dealing with API boundaries and other behind-the-scenes kinda stuff.

# type assertion

See Type assertions @ Basic Types - www.typescriptlang.org

Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does.

Type assertions are a way to tell the compiler "trust me, I know what I’m doing."

A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler.

The "angle-bracket" syntax :

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
1
2
3

The as syntax :

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;
1
2
3

The first is the legacy one and the second has been added to be compatible with JSX (TSX with TypeScript).

The first one clashed with JSX syntax.

The second is now considered to be the syntax to use.

type-assertion - basarat.gitbooks.io/typescript

What are the difference between these type assertion or casting methods in TypeScript - stackoverflow.com - 20180413

# type aliases

See Type aliases @ Types Aliases - www.typescriptlang.org

TSLint callable-types

An interface or literal type with just a call signature can be written as a function type.

Interface vs Type alias in TypeScript - Martin Hochel - 20180312

what’s the difference between using type and interface for defining compile time types within TypeScript

official documentation is obsolete since TS@2.1 :

  • errors messages display type alias name correctly
  • types aliases can be extended (extended by an interface or implemented by a class)
  • types aliases can be used for type alias extension via intersection operator &

# type guards

See Type Guards and Differentiating Types - www.typescriptlang.org

A type guard is some expression that performs a runtime check that guarantees the type in some scope.

Checking the type of an object in Typescript: the type guards - medium.com/ovrsea - 20181122

What does the is keyword do in typescript? - stackoverflow.com - 20161017

function isString(test: any): test is string{
    return typeof test === 'string';
}

function example(foo: any){
    if(isString(foo)){
        console.log('it is a string' + foo);
        console.log(foo.length); // string function
    }
}
example('hello world');
1
2
3
4
5
6
7
8
9
10
11

Inside a type guard function, we can check :

  • that a property exist with the in keyword ('propname' in foo where foo is the object passed to the type guard function to check onto)
  • that a property is from the typeof number, string, boolean, or symbol and nothing more.

It is impossible to check if a property is from the typeof a custom type because there is not the required meta data available at runtime.

# unknown type

The purpose of the unknown type is to force the developper to check the structure with type guards or assert the type with type assertion.

See what's new in TS@3.0 - www.typescriptlang.org

unknown is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

TypeScript 3.0: Exploring Tuples and the Unknown Type - auth0.com/blog - 20180821

We can use an unknown type if and only if we perform some form of checking on its structure. We can either check the structure of the element we want to use or we can use type assertion to tell TypeScript that we are confident about the type of the value

# Dependency Injection with TypeScript

# Using the classical functional style

Using type aliases to define function types.

In vanilla JavaScript it is dead simple, we use curried function and partial application :


// dependency function without deps

const doSomethingElse = (arg) => {
    // impl ...
}

export default doSomethingElse;

// function with dependency

const makeDoSomething = ({ doSomethingElse }) => (arg) => {
    // impl ...

    const value = doSomethingElse(111);

    // impl ...
}

export default makeDoSomething;

// caller

import doSomethingElse from './do-something-else'
import makeDoSomething from './make-do-something'

const doSomething = makeDoSomething({ doSomethingElse });

doSomething('toto');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

In TypeScript we can use the same pattern, and we can type dependencies using Type Aliases.

We could prefer type aliases instead of interfaces because of TSLint callable-types rule.

The same example as above in TypeScript becomes :


// dependency function without deps

export type DoSomethingElseFnType = (arg: number) => number;

export function doSomethingElse(arg: number): number {
    // impl ...
}

// function with dependency

import { DoSomethingElseFnType } from './do-something-else';

export type = DoSomethingFnType = (arg: string) => string;

export function makeDoSomething(deps: { doSomethingElse: DoSomethingElseFnType}): DoSomethingFnType {
    return (arg: string): string => {
        // impl ...

        // you reach your dependency through deps destructuring and benefit from typing :
        const value = deps.doSomethingElse(111);

        // impl ...
    }
}

// caller

import { doSomethingElse } from './do-something-else';
import { DoSomethingFnType, makeDoSomething } from './make-do-something';

const doSomething: DoSomethingFnType = makeDoSomething({ doSomethingElse });

doSomething('toto');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Using POO style by hand

You don't need any typing here, because obviously a class is a type.


// dependency class without deps

export class SomethingElse {
    public static makeSomethingElse(): SomethingElse {
        return new SomethingElse();
    }

    private constructor() {

    }

    public doSomethingElse(arg: number): number {
        // impl ...
    }
}

// class with dependency

import { SomethingElse } from './something-else';

export class Something {
    public static makeSomething(deps: {somethingElse: SomethingElse}): Something {
        return new Something(deps);
    }

    private somethingElse: SomethingElse;

    private constructor(deps: {somethingElse: SomethingElse}) {
        this.somethingElse = deps.somethingElse;
    }

    public doSomething(arg: string): string {
        const value = this.somethingElse.doSomethingElse(111);

        // impl ...
    }
}

// caller

import { SomethingElse } from './something-else';
import { Something } from './do-something';

const somethingElse: SomethingElse = SomethingElse.makeSomethingElse();
const something: Something = Something.makeSomething({ somethingElse });

something.doSomething('toto');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# Using POO style with decorators

See decorators from www.typescriptlang.org/docs/handbook.

Dependency Injection in TypeScript - 20180205

InversifyJS - github.com/inversify

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.

# Tricks

# Nominal typing

Nominal typing techniques in TypeScript - michalzalecki.com - 20171227 - Michal Zalecki

  • Approach #1: Class with a private property
class USD {
  private __nominal: void;
  constructor(public value: number) {};
}

class EUR {
  private __nominal: void;
  constructor(public value: number) {};
}

const usd = new USD(10);
const eur = new EUR(10);

function gross(net: USD, tax: USD) {
  return { value: net.value + tax.value } as USD;
}

gross(usd, usd); // ok
gross(eur, usd); // Error: Types have separate declarations of a private property '__nominal'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • Approach #2: Brands
interface USD {
  _usdBrand: void;
  value: number;
}

interface EUR {
  _eurBrand: void;
  value: number;
}

let usd: USD = { value: 10 } as USD;
let eur: EUR = { value: 10 } as EUR;

function gross(net: USD, tax: USD) {
  return { value: net.value + tax.value } as USD;
}

gross(usd, usd); // ok
gross(eur, usd); // Error: Property '_usdBrand' is missing in type 'EUR'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • Approach #3: Intersection types
class Currency<T extends string> {
  private as: T;
}

type USD = number & Currency<"USD">
type EUR = number & Currency<"EUR">

const usd = 10 as USD;
const eur = 10 as EUR;

function gross(net: USD, tax: USD) {
  return (net + tax) as USD;
}

gross(usd, usd); // ok
gross(eur, usd); // Error: Type '"EUR"' is not assignable to type '"USD"'.

// ...

function ofUSD(value: number) {
  return value as USD;
}

function ofEUR(value: number) {
  return value as EUR;
}

const usd = ofUSD(10);
const eur = ofEUR(10);

function gross(net: USD, tax: USD) {
  return ofUSD(net + tax);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  • Approach #4: Intersection types and brands
type Brand<K, T> = K & { __brand: T }

type USD = Brand<number, "USD">
type EUR = Brand<number, "EUR">

const usd = 10 as USD;
const eur = 10 as EUR;

function gross(net: USD, tax: USD): USD {
  return (net + tax) as USD;
}

gross(usd, usd); // ok
gross(eur, usd); // Type '"EUR"' is not assignable to type '"USD"'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

This PR in typescript repo will maybe land.

# error typing

Get a catch block error message with TypeScript

The default type for the variable exposed by a catch clause is unknown because in JavaScript we can throw anything.

So the TS compiler emit this error :

Catch clause variable type annotation must be 'any' or 'unknown' if specified. ts(1196)
1

When we try to type a catch clause like this :

try {
  throw new Error('Oh no!')
} catch (error: Error) {
  // we'll proceed, but let's report it
  reportError({message: error.message})
}
1
2
3
4
5
6

So, we cannot type a variable in a catch clause. We need to check type variable type manually :

try {
  throw new Error('Oh no!')
} catch (error) {
  let message = 'Unknown Error'
  if (error instanceof Error) message = error.message
  // we'll proceed, but let's report it
  reportError({message})
}
1
2
3
4
5
6
7
8