TypeScript
Présentations
Florent Berthelot
Consultant - Viseo
Consultant Formateur - Zenika
Consultant Formateur - Freelance (WeFacto, Human Coder)
(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).
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
Import Type
import { APIResponseType } from "./api";
// Explicitly use import type
import type { APIResponseType } from "./api";
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
};
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 3 types de Pokémon (Éclair, Feu et Plante)
Les Pokémons de type éclair sont les premiers à attaquer
Les Pokémons de type feu ont un bonus d'attaque de 50% sur les type plante.
Exercice 7 - plus précis si besoin
Pikachu est de type éclair
Salamèche est de type feu
Bulbizarre est de type plante
Ponyta est de type feu
Nouveaux scénarios :
Quand Ponyta affronte Pikachu
Alors Pikachu gagne grâce à sa rapidité et sa forte attaque
Quand Ponyta affronte bulbizarre
Alors Ponyta gagne grâce à son bonus d'attaque
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 ?!
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
Formation TypeScript
- 929