TypeScript

Présentations

Florent Berthelot


Consultant - Viseo

Consultant Formateur - Zenika

Consultant Formateur - Freelance (WeFacto, Human Coder)
 

 

berthelot.io

(Attention site très sobre)

Déroulement

des 2 jours

Matin :            9h - 12h30 - Émargement

Après-midi : 14h - 17h30 - Émargement

 

Attention, vous êtes noté !

Attention, je suis noté !

 

TP Fil rouge

La correction des TPs en fin de formation

TypeScript,

qu’est ce que c’est ?

Qu'est ce que c'est ?

Langage créé par Anders Hejlsberg en 2012

Projet open-source maintenu par Microsoft

Pourquoi ?

Rends le typage explicite

Ajoute une couche de test (Compilation vs Runtime)

Feedback rapide grâce aux IDE



 

Moyen de pression par Microsoft pour ajouter des nouvelles fonctionnalités dans JavaScript

Qui ?

Angular (Google)


Deno (Créateur de node.js)

 

....

Beaucoup, beaucoup !

Concurence ?

Elm - Plus strict que TypeScript

 

Flow - Facebook, projet mort ?

 

JsDoc - Pas vraiment un langage

Comment ?

TypeScript est un superset de JavaScript

 

Tout code JavaScript est un code TypeScript (en principe)


Compile en JavaScript

Outillages

Node.js

J'suis un developpeur C qui a problèmes asynchrones des

 

-Ryan Dahl-

Node.js

+ API Système

Outillage Node.js

Exercice 1

  • Créer un nouveau projet
  • Initialisez le projet avec NPM
  • Installez TypeScript sur le projet
  • Créez ce fichier
// src/index.js
console.log('hello');
  • Créez un script npm "start" qui exécute ce fichier

Rappels JavaScript

Outillages II

TypeScript

Compilateur

$ npm install -g typescript

$ tsc file.ts

Génère un fichier file.js

$ npm install -g typescript

$ tsc file.ts

TypeScript

Compilateur

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false,
        "outDir": "dist"
    },
    "include": [
        "src/**/*"
    ]
}
// Se génère avec tsc --init
$ tsc

Babel VS TypeScript

Qu'est ce que Babel ?

Babel VS TypeScript

TypeScript transpile vers une version spécifique de JS

Babel transpile vers une version spécifique de JS

Babel est plus rapide
Babel ne vérifie pas les Types

TypeScript et IDE

Exercice 2

  • Renommez le fichier src/index.js en src/index.ts
  • Initialisez TypeScript via la commande
    npx -p typescript tsc --init
  • Ajoutez un NPM Script "Build" qui compile les fichiers TS vers un dossier build/
  • Observez la différence entre le fichier JS et TS

Outillages III

Les tests avec Jest

Exercice 3

  • Installez Jest sur votre projet
  • Écrivez la description de vos tests avec it.todo a partir des scénarios suivants  (slide suivant)
  • Ajoutez des scénarios de test au besoin

 

Scénario 1:
   Quand Pikachu et Salamèche s'affrontent

   Alors Pikachu est le premier à attaquer

 

Scénario 2:

   Quand Pikachu et Salamèche s'affrontent

   Alors Pikachu gagne

 

Scénario 3:

   Quand Pikachu et Bulbizarre s'affrontent

   Alors Bulbizarre gagne

 

Scénario 4:

   Quand Pikachu et Bulbizarre s'affrontent

   Alors Pikachu est le premier à attaquer

Les Types

Les types

var variableName: variableType = value;

let variableName2: variableType = value;

const variableName3: variableType = value;

TypeScript est un superset de JavaScript

Rend le typage JavaScript Explicite

 

Et c'est quasiment la seul chose a savoir

Les Types primitifs

Subtitle

const pi: number = 1;

const name: string = 'toto';

const canYouReadThis: boolean = true;

let useless: undefined;

const covid: null = null;


useless = 1; // erreur de compilation

Types par références

Subtitle

const schools: string[] = ['ESGI', 'EFREI', 'EPITA'];

const schools2: Array<string> = ['ESGI', 'EFREI', 'EPITA'];

schools.push('EPITECH');	// OK
schools.push(42);		// KO


const user1: {name: string, roles: string[]} = {
  name: 'Florent Berthelot',
  roles: []
};


user1.roles.push('Admin'); 	// OK
user1.roles.push(42); 		// KO

Any et Unknown

Subtitle

let notSure: any = 4;
notSure = "maybe a string instead";

console.log(notSure.toto); // Erreur d'execution

notSure = {toto: 42};
console.log(notSure.toto); // OK


let notSureSafe: unknown = notSure;

console.log(notSureSafe.toto); // Erreur de compilation

Les fonctions

Subtitle

function namedFunction(arg1: string, arg2?: number, arg3: string = 'hello'): string {
  return `${arg3}, ${arg1} - ${arg2}`;
}



let concat: (arg1: string, arg2: number) => string = (arg1, arg2) => arg1 + arg2;

Les fonctions

Subtitle

function printNumber(x: number): void {
    console.log(x);
}

function error(message: string): never {
    throw new Error(message);
}


function infiniteLoop(): never {
    while (true) {
    }
}

Inférence de type

TypeScript essai de deviner quel est le type et ne typera en any qu'en dernier recours

const theAnswer = 42; // Type number

function identity(name = 'EFREI') { // name est de type string
  return name; // Type string
}

Trichez !

Exercice 4

  • Implémentez le code et les tests écrit lors de l'exercice 3
  • Complétez les cas de tests au besoin
  • Mettez `export` devant chacune de vos fonctions

Faites simple au début, avec des attributs attack, speed, et HP (health point).

Sinon : https://www.dragonflycave.com/mechanics/battle

Les classes

Les classes

Les classes

class Human {
  protected nbOfTimeDisplayed = 0;
  public message: string = "bonjour";
  private age: number;

  constructor(age: number) {
      this.age = age;
  }

  displayAge():void {
    console.log(this.age);
    this.nbOfTimeDisplayed++
  }
}

const florent: Human = new Human(18);
florent.displayAge();

Les classes

class Human {
  protected nbOfTimeDisplayed = 0;
  public message: string = "hello";

  constructor(private age: number) {}

  displayAge():void {
    console.log(this.age);
    this.nbOfTimeDisplayed++
  }
}

Les classes

abstract class Human {
  scientificName: string = 'homo-sapiens'
  
  abstract age: number
  abstract work():void
}


new Human(); // KO


class Developer extends Human {
  age = 40
  work() { console.log('hello world'); }
}

new Developer() // OK

Les classes

Rappel JavaScript, les prototypes

http://berthelot.io/slides/javascript.html#/3/22

Les interfaces

Les interfaces

interface Duck{
  color: string
  fly(height: number): void
  quack: (message: string) => void
}

const duck: Duck = {
  color: 'yellow',
  fly: height => {},
  quack: message => {}
}

Les interfaces

interface SayHello {
  (message: string): string;
}

let sayHello: SayHello;

sayHello = function(source: string): string {
  return source.toLowerCase();
}

Les interfaces

interface Person {
  sayHello(message: string): void;
}

class Adult implements Person {
  sayHello(message: string): void {
    console.log(`Hello ${message}`);
  }
}

Exercice 5

  • Créez une interface Pokemon
  • Créez une classe Battle

Organiser son code grâce aux modules

ES Modules

EsModule + TypeScript

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "incremental": true,
    "esModuleInterop": true,
    "module": "ESNext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

EsModule + TypeScript

import type {PokemonInterface} from './pokemon.interface'

Exercice 6

Découpez votre code en plusieurs fichiers

Retour sur les Types

Enums

const enum Music { Rap, Rock, Blues };
enum AttackTypes {Fire = 2, Water = 5, thunder = 8}; 

const a: Music = Music.Rap;
const burn = AttackTypes.Fire;

const FireName: string = AttackTypes[AttackTypes.Fire]; // "Fire"
console.log(AttackTypes[5]); // KO

Le const devant l'enum optimise la taille du build,

mais perd en dynamisme.

Tuples

tableau de taille fini

let attackDamage: [string, number];

attackDamage = ["lightning", 42]; // OK


attackDamage = [42, "lightning"]; // KO

Union Type

Particulièrement utile en mode strict

let stringAndNumber: string|number;

stringAndNumber = 1; // OK
stringAndNumber = 'string'; // OK

Optional Properties

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}

class Dog {
  beautiful?: boolean
}

Alias

type arrayOfPrimitives = Array<number | string | boolean | null | undefined>;


type string = number; // 🤡
type X = {
    a: number
    b: string
};

Import Type

import { APIResponseType } from "./api";

// Explicitly use import type
import type { APIResponseType } from "./api";

Alias VS Interfaces

interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

Alias VS Interfaces

interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };
type Point = { x: number; }
type Point = { y: number; } // KO

Alias VS Interfaces

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type PartialPoint = { x: number; } | { y: number; };

// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
  x = 1;
  y = 2;
}

Alias VS Interfaces

// primitive
type Name = string;

interface Name {
  name: string;
}

Types Guard

let stringOrStringArray: string|string[];
/**...**/

if (typeof stringOrStringArray === 'string') {
  console.log(stringOrStringArray.toLowerCase()); //OK
}

if (stringOrStringArray instanceof String) {
  console.log(stringOrStringArray.toLowerCase()); //OK
}

console.log(stringOrStringArray.toLowerCase()); //KO


let point: {x: string} | {y: string};
/**...**/
if('x' in point) {
  console.log(point.x);
} else {
  console.log(point.y);
}

Types Guard


function isString(x: any): boolean {
  return x instanceof String;
}
if (isString(stringOrStringArray)) {
  console.log(stringOrStringArray.toLowerCase()); //OK
}

/****/

function isString(x: any): x is string {
  return x instanceof String;
}
if (isString(stringOrStringArray)) {
  console.log(stringOrStringArray.toLowerCase()); //OK
}

Exercice 7

  • Gérez deux types de pokémon (Feu et Glace)

 

Les pokemons de types feu sont les premiers à attaquer

Les pokemon de type glace on un bonus d'attaque de 50% sur les type feu.

 

 

Les librairies JavaScript dans un projet TypeScript

Node & TypeScript

Les librairies disponible sur NPM ne sont qu'en JavaScript.

 

Les Librairies exposent alors des fichiers d.ts en plus du JS.

 

Pour générer les fichiers .d.ts : tsc --declaration

// Package.json
{
    "name": "typescript-lib",
    "types": "./lib/main.d.ts"
}

Node & TypeScript

Rappelez-vous de :

$ npm i -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest

Que se passe t'il sans @types/jest ?

Node & TypeScript

Rappelez-vous de :

$ npm i -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest

Que se passe t'il sans @types/jest ?

Exemple de d.ts

declare var angular: angular.IAngularStatic;
export as namespace angular;

declare namespace angular {
  interface IAngularStatic {
    ...
    bootstrap(
      element: string|Element, modules?: Array<string|Function|any[]>,
      config?: IAngularBootstrapConfig): auto.IInjectorService;
  }
}

Les génériques

Génériques

function identityA<Type>(arg: Type): Type {
  return arg;
}

// VS 

function identityB(arg: unknown): unknown {
  return arg;
}


identityA(2).toFixed(); // OK
identityB(2).toFixed(); // KO

Génériques

class List<T, Index=number> {
  add(value: T) {
    ...
  }
  splice(value: T, index: Index){

  }
}

const numericArray = new List<number>();

numericArray.add(5); 
numericArray.splice(5, 2);

numericArray.add('hello'); // compilation err

Génériques

interface Pokemon {
  name: string;
}
 
function loggingIdentity<Type extends Pokemon>(arg: Type): Type {
  console.log(arg.name);
  return arg;
}

Exercice 8

  • Créez un service de log
  • Si ce que l'on log est un pokémon, alors on log:
    • "<PokemonName> : <nbPV> PV"
  • Sinon le log n'est pas formaté

 

 

N'oubliez pas d'ajouter un mock de console pour ne pas polluer vos tests

Trucs et astuces

Truc et astuces

Type literal

type PokemonTypes = "fire" | "ice" | "lightning";

type PokemonId = `${PokemonTypes}-${string}`


const pikaId: PokemonId = 'lightning-pickachu';

Truc et astuces

Type numeral

type Dice = 1 | 2 | 3 | 4 | 5 | 6;


const diceResult: Dice = 6;

C'est la base du Safe By Pattern

Truc et astuces

Safe By pattern

type Pokemon = {
  name: string 
};

type PokemonResponse = {
  name: Pokemon['name'],
  isAlive: boolean
};

Truc et astuces

Safe By pattern

const pickachu = {
  name: 'pika'
}

type PokemonName = typeof pickachu['name']; // string

Truc et astuces

Safe By pattern

type PokemonFire = {
  id: `fire-${string}`,
  name: string,
  type: 'fire'
};

type PokemonIce = {
  id: `ice-${string}`,
  name: string,
  type: 'ice'
};

type Pokemon = PokemonFire | PokemonIce;

Satisfait ?

as const

const theAnswer = 42; // Infer type number

const theRealAnswer: 42 = 42; // Type plus précis

const theRealAnswerInfer = 42 as const;


// Fonctionne de manière profonde
const aComplexeExemple = {
  theAnswer,
  theRealAnswer,
  test: 'coucou',
  glou: {
    ton: [true, 42, 'tuple']
  }
} as const

/*
{
  theAnswer: number,
  theRealAnswer: 42,
  test: 'coucou',
  glou: {
     ton: [true, 42, 'tuple']
  }
}

En readonly !
*/

as

const theAnswer = 42 as string; // Error

const theAnswer2 = 42 as unknown; // unknown !

const theAnswer3 = 42 as const as number; // Number

const theAnswer4 = 42 as unknown as 43; // 43

type Dish = {
  alcool: boolean
}

const blanquette = {alcool: true, yummy: true} as Dish

Satisfies

type posX = {
  x: number
}
type posY = {
  y: number,
}
type PartialPosition = posX | posY


const asExample = {x: 1} as PartialPosition
// asExemple: PartialPosition

const satisfieExample = {x: 1} satisfies PartialPosition
// satisfieExample: {x: number}

Exercice 9

Appliquez les principes du Safe By Pattern dans votre code.


Contrainte supplémentaire : Les pokemons ont un ID qui commence forcément par le type du pokemon et son nom.

Exemple : feu-salemèche

 

 

Les décorateurs

Décorateurs de props

function log(target: any, propertyKey: string): any {
  console.log(`Person with default message ${target[propertyKey]}`);
}

class Person {
  @log
  private message: string = 'Hello';
}

Décorateurs de class

function log<T extends {new(...args:any[]):{}}>(constructor: T): T {
  return class extends constructor {
    protected additionalProperty: string = 'Hello';

    constructor() {
      super();
      console.log(`Person constructor called with ${this.additionalProperty}`);
    }
  }
}

@log
class Person {
  /* ... */
}

Décorateurs de méthode

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
 
  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

Décorateurs de paramètre de méthode

function log(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  console.log(`La méthode ${propertyKey} a le paramètre ${parameterIndex} bien décoré`);
}

class BugReport {
  title: string;
  
  constructor(@log t: string) {
    this.title = t;
  }
}

Décorateurs

! Attention !
! Non standard !

Décorateurs

! Attention !
! Non standard !

npm install --save-dev @babel/plugin-proposal-decorators
// babel.config.js

module.exports = {
    presets: [
      ['@babel/preset-env', {targets: {node: 'current'}}],
      '@babel/preset-typescript'
    ],
    "plugins": [
      ["@babel/plugin-proposal-decorators", { "legacy": true }]
    ]
  };

Exercice 10

  • Créer un décorateur @log() qui log chaque information de bataille
  • Bonus : Utilisez la librairie Chalk pour colorer vos logs en fonction d'un paramètre donné au décorateur

 

 

L'asynchrone

Les callbacks

Les Promesses

Async / Await

Promise.finally, boucle for await, ...

Et TypeScript ?

type Account = {id: string, accountName: string}

function getAccount(): Promise<Account> {
  return Promise.resolve({
    id: '471',
    accountName: 'Hold Account'
  })
}



async function ResponseOfARequest () {
    const response: Awaited<ReturnType<typeof getAccount>> = await getAccount()
}

Exercice 10

Dorénavant, un Pokémon n'a le droit d'attaquer qu'une fois par seconde maximum

 

 

Pour aller plus loin

Pour aller plus loin

type PokemonType = 'Fire' | 'Ice'

const hasAdvantageAgainst: Record<PokemonType, PokemonType> = {
    Fire: 'Ice',
    Ice: 'Fire'
}

const countryLangagueMapper = {
    France: 'french',
    Belgique: 'french'
} as const

type Country = keyof typeof countryLangagueMapper // 'France' | 'Belgique'

Turing ?!

https://github.com/cassiozen/TDungeon

Les nouveautés ?

TC39

What's New ?

Fin

Florent Berthelot

https://berthelot.io

florent@berthelot.io

Correction

Bonus

Le documentaire

Formation TypeScript

By Florent Berthelot