Florent Berthelot
Consultant - Viseo
Consultant Formateur - Zenika
Consultant Formateur - Freelance (WeFacto, Human Coder)
(Attention site très sobre)
Matin : 9h - 12h30 - Émargement
Après-midi : 14h - 17h30 - Émargement
Attention, vous êtes noté !
Attention, je suis noté !
La correction des TPs en fin de formation
Langage créé par Anders Hejlsberg en 2012
Projet open-source maintenu par Microsoft
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
Angular (Google)
Deno (Créateur de node.js)
....
Beaucoup, beaucoup !
Elm - Plus strict que TypeScript
Flow - Facebook, projet mort ?
JsDoc - Pas vraiment un langage
TypeScript est un superset de JavaScript
Tout code JavaScript est un code TypeScript (en principe)
Compile en JavaScript
J'suis un developpeur C qui a problèmes asynchrones des
-Ryan Dahl-
// src/index.js
console.log('hello');
$ npm install -g typescript
$ tsc file.ts
Génère un fichier file.js
$ npm install -g typescript
$ tsc file.ts
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"outDir": "dist"
},
"include": [
"src/**/*"
]
}
// Se génère avec tsc --init
$ tsc
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
npx -p typescript tsc --init
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
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
const pi: number = 1;
const name: string = 'toto';
const canYouReadThis: boolean = true;
let useless: undefined;
const covid: null = null;
useless = 1; // erreur de compilation
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
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
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;
function printNumber(x: number): void {
console.log(x);
}
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
}
}
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
}
Faites simple au début, avec des attributs attack, speed, et HP (health point).
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();
class Human {
protected nbOfTimeDisplayed = 0;
public message: string = "hello";
constructor(private age: number) {}
displayAge():void {
console.log(this.age);
this.nbOfTimeDisplayed++
}
}
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
Rappel JavaScript, les prototypes
http://berthelot.io/slides/javascript.html#/3/22
interface Duck{
color: string
fly(height: number): void
quack: (message: string) => void
}
const duck: Duck = {
color: 'yellow',
fly: height => {},
quack: message => {}
}
interface SayHello {
(message: string): string;
}
let sayHello: SayHello;
sayHello = function(source: string): string {
return source.toLowerCase();
}
interface Person {
sayHello(message: string): void;
}
class Adult implements Person {
sayHello(message: string): void {
console.log(`Hello ${message}`);
}
}
{
"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"]
}
import type {PokemonInterface} from './pokemon.interface'
Découpez votre code en plusieurs fichiers
import { APIResponseType } from "./api";
// Explicitly use import type
import type { APIResponseType } from "./api";
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.
tableau de taille fini
let attackDamage: [string, number];
attackDamage = ["lightning", 42]; // OK
attackDamage = [42, "lightning"]; // KO
Particulièrement utile en mode strict
let stringAndNumber: string|number;
stringAndNumber = 1; // OK
stringAndNumber = 'string'; // OK
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
class Dog {
beautiful?: boolean
}
type arrayOfPrimitives = Array<number | string | boolean | null | undefined>;
type string = number; // 🤡
type X = {
a: number
b: string
};
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
type Point = { x: number; }
type Point = { y: number; } // KO
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;
}
// primitive
type Name = string;
interface Name {
name: string;
}
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);
}
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
}
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.
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 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"
}
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 ?
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 ?
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;
}
}
function identityA<Type>(arg: Type): Type {
return arg;
}
// VS
function identityB(arg: unknown): unknown {
return arg;
}
identityA(2).toFixed(); // OK
identityB(2).toFixed(); // KO
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
interface Pokemon {
name: string;
}
function loggingIdentity<Type extends Pokemon>(arg: Type): Type {
console.log(arg.name);
return arg;
}
N'oubliez pas d'ajouter un mock de console pour ne pas polluer vos tests
type PokemonTypes = "fire" | "ice" | "lightning";
type PokemonId = `${PokemonTypes}-${string}`
const pikaId: PokemonId = 'lightning-pickachu';
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
const diceResult: Dice = 6;
C'est la base du Safe By Pattern
type Pokemon = {
name: string
};
type PokemonResponse = {
name: Pokemon['name'],
isAlive: boolean
};
const pickachu = {
name: 'pika'
}
type PokemonName = typeof pickachu['name']; // string
type PokemonFire = {
id: `fire-${string}`,
name: string,
type: 'fire'
};
type PokemonIce = {
id: `ice-${string}`,
name: string,
type: 'ice'
};
type Pokemon = PokemonFire | PokemonIce;
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 !
*/
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
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}
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
function log(target: any, propertyKey: string): any {
console.log(`Person with default message ${target[propertyKey]}`);
}
class Person {
@log
private message: string = 'Hello';
}
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 {
/* ... */
}
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;
}
}
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;
}
}
! Attention !
! Non standard !
! 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 }]
]
};
Promise.finally, boucle for await, ...
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()
}
Dorénavant, un Pokémon n'a le droit d'attaquer qu'une fois par seconde maximum
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'
Florent Berthelot
https://berthelot.io
florent@berthelot.io