Comprendre Typescript et ES6 en une heure

Une présentation rapide de TypeScript et ES6, il existe de très bonnes présentations très complètes de TypeScript et ES6.

Dans les exemples ci-dessous on ne fait pas la différence entre ce qui est spécifique à ES6 et ce qui est spécifique à TypeScript (on peut aussi utiliser TypeScript avec ES5). En sachant toutefois que la notion de type n’existe qu’en TypeScript.

Mise en place des outils

Créer un répertoire testTypeScript.

Ouvrir une fenêtre de commandes (une console) et se placer dans le répertoire testTypeScript.

Installer TypeScript :

npm install typescript

Ajouter un fichier index.html

<html>
        <head>
        </head>
        <body>
            <h1>Test TypeScript</h1>
            <script src="test.js"></script>

        </body>
</html>

Créer un fichier test.ts

let texte : string = "Bonjour";
document.write(texte);

Lancer la compilation du fichier TypeScript dans la console.

node_modules\.bin\tsc --target es6 test.ts

On aura pu éviter de spécifier le chemin vers TypeScript en installant TypeScript de manière globale.

Le résultat sera un fichier JavaScript ES6 test1.js. C’est ce fichier qui est inclus par la page HTML.

Résultat de la génération :

let texte = "Bonjour";
document.write(texte);

Il suffit d'afficher la page index.html dans votre navigateur favori pour voir le résultat.

Pour les exemples suivants il suffit de modifier le fichier test.ts puis de générer le fichier Javascript en lançant la même commande.

Déclaration des variables

Les nombres

let y : number;
y = -1;
document.write(y.toString());

Les chaînes

Pas besoin de déclarer le type (string) si l'initialisation et la déclaration se font en même temps.

let nom = 'durant';
document.write(nom);

On peut utiliser l'interpolation de chaîne (string interpolation) pour mixer des variables et des chaînes en évitant les +. c'est le caractère ` qui définit une string interpolation.

let s1 : string = `bonjour ${nom}. Comment allez-vous ?`;
document.write(s1);

Les enums

l'énum comme la classe définit un nouveau type.

enum lg {"français", "anglais"};
let langue : lg;
langue = lg.anglais;

Any

Si vous ne connaissez pas le type vous pouvez utiliser any.

let o : any;
o = "bonjour";
o = 1;

Différence entre let, const et var

La différence intervient surtout au niveau de la portée de la variable.

Une variable définie avec let aura une portée dans le bloc où elle est définie comme en Java et C#.

Une variable définie avec var aura une portée dans la fonction où elle est définie.

Une variable const se rapproche de let mais ne peut pas être modifiée après sont initialisation.

function test1(n : number):void
{
    for (let index = 0; index < n; index++) {
    document.write("hello");
}
//document.write(index.toString());  ERREUR index n'est pas connu

}

les tableaux

Création d'un tableau

let tab: number[] = [];
//On ajoute 5 comme premier élément
tab.push(5);

//On ajoute quatre 10.
for (var index = 1; index < 5; index++) {
    tab.push(10);
}

//on parcourt le tableau avec une boucle for of
for (const element of tab) {
    document.write(element.toString());
}
//On affiche 5 10 10 10 10

//peut aussi récupérer des éléments du tableau dans des variables (array destructuring es6)
let [element1, element2, element3,element4, element5 ] = tab;
document.write(element1.toString());
document.write(element2.toString());
//Affiche 5 et 10
//On crée directement des variables qui contiennent les valeurs issues du tableau.

Les fonctions

Le type de retour est spécifié derrière les deux points.

function multiplierPar2(nb : number): number
{
    return nb * 2;
}

document.write(multiplierPar2(232).toString());

Définir une variable de type fonction

let fonction1 : (nb : number) => number;

fonction1 = multiplierPar2;

La fonction peut avoir des paramètres faculatifs avec le point d'interrogation.

function multiplier(nb1 : number, nb2? : number): number
{
    if (nb2)
    {
        return nb1 *nb2;
    }
    else
    {
        return nb1 * 2;
    }
}

//On peut maintenant utiliser la fonction multiplier de deux façons
document.write(multiplier(1000).toString());
document.write(multiplier(1000, 300).toString());

Les fonctions fléchées

Les fonctions fléchées sont surtout utilisées pour les callbacks ou en paramètre de fonction.

       
function test(fonc1 : (nb : number) => number, nb : number):number
{
    return fonc1(nb);
}


let result1 = test(x => x *4, 5);
let result2 = test(x => x *8, 5);

document.write(result1.toString());
document.write(result2.toString());
//affiche 20 et 40

Les fonctions fléchées sont un raccourci pour déclarer une fonction anonyme : function(x : number){return x * 4;}

Les fonctions fléchées sont plus intéressantes que les fonctions anonymes car elles permettent d'utiliser sans problème this.

Parcourir un tableau

On ne va pas souvent parcourir les tableaux avec for...of, la plupart du temps quand cela sera possible on utilisera les puissantes méthodes map, filter, reduce. On reprend le tableau tab avec 5 10 10 10 10.

map effectue une opération sur tous les éléments du tableau et retourne un nouveau tableau.

let tab1 = tab.map(x => x + "*");

for (const item of tab1) {
    document.write(item);
}

On va obtenir un nouveau tableau tab1 contenant 5* 10* 10* 10* 10*

filter va filtrer les éléments du tableau suivant une condition et retourner un nouveau tableau.

let tab2 = tab.filter(x => x > 6);

for (const item of tab2) {
    document.write(item.toString());
}

On va obtenir un nouveau tableau tab2 contenant 10 10 10 10

reduce va permettre de parcourir le tableau pour obtenir un résultat.

reduce peut prendre en paramètre 4 éléments, dans l'exemple nous utilisons les deux plus importants : previous qui est la valeur accumulée par les précédents appels et current est la valeur en cours du tableau.

let somme = tab.reduce((previous, current) => previous + current);
document.write(somme.toString());

On va obtenir la somme de tous les élements du tableau : 45

Les dictionnaires

On peut utiliser des dictionnaires (clé/valeur) avec Map (ES6).

 
let m: Map = new Map();
m.set(1, "abc");
m.set(2, "def");

for (const [cle,valeur] of m.entries()) {
    document.write(valeur);
}

On utilise un tableau destructuré pour récupérer la clé et la valeur.

On peut aussi récupérer les clés dans un tableau (map.keys()) ou les valeurs (map.values()).

Set, un Set est une liste qui ne contient aucun doublon.

Set est utile si vous voulez rechercher une valeur dans une liste rapidement.

let s : Set = new Set();
s.add(33);
s.add(66);
//Recherche dans le set
s.has(33);
//Retourne un booléen

s.add(33);
//Si on ajoute une valeur qui existe déjà elle ne va pas être ajoutée, la taille (size) sera toujours de 2.

//On peut parcourir le tableau avec for..of
for (const item of s) {
    document.write(item.toString());
}
    

Les classes

On peut désormais définir des classes comme en C# ou en Java.

class Personne
{
private _nom : string;
private anneeNaissance : number;

//Le constructeur
constructor(nom: string, anneeNaissance : number )
{
    this._nom = nom;
    this.anneeNaissance = anneeNaissance;
}
//Syntaxe particulière pour les getter et setter. On n'a pas besoin d'appeller la méthode get ou set.
//On a directement accès à la variable.
get nom() : string
{
    return this._nom;
}

set nom(leNom : string)
{
    this._nom = leNom;
}

public CalculAge() : number
{
    return 2017 - this.anneeNaissance;
}

}

let unePersonne = new Personne("durant", 2000);
document.write(unePersonne.nom);
document.write(unePersonne.CalculAge().toString());

Les autres aspects de la programmation objet en Javascript (classe abstraite, héritage...) sont très similaires à Java et C#.

Les interfaces

Les interfaces ne jouent pas vraiment le même rôle qu'un C# ou Java. L'interface sert surtout à décrire les types de données qui vont être utilisés. La classe n'a pas toujours besoin d'implémenter l'interface car Javascript se base sur la structure pour déterminer le type. Javascript analyse si les champs de l'interface sont les mêmes que ceux de la classe et si oui il considère que l'interface est implémenté.

interface controle
{
    longueur? : number;
    largeur : number
}

function tester(c : controle) : number
{
    if (!c.longueur) c.longueur = 10;
    return c.largeur * c.longueur;
}
let c1 : controle = {longueur : 10, largeur : 10};

document.write(tester(c1).toString());

Les interfaces peuvent aussi contenir des méthodes

interface calculActif
{
    numero : number;
    Calcul : (x:number) => number
}

let c2 : calculActif = {numero : 9, Calcul(x) {return this.numero *x}};
                 
document.write(c2.Calcul(10).toString());
               

Une classe peut aussi implémenter l'interface.

class check implements calculActif
{ 
    numero : number;
    constructor(n: number)
    {
        this.numero = n;
    }

    Calcul(x : number): number
    {
        return this.numero * x;
    }

    Afficher(v : number)
    {
        document.write(this.Calcul(v).toString());
    }
}

let c3 : check  = new check(3);
c3.Afficher(33);

Les symboles

Ils permettent de définir un identifiant unique, un peu comme un guid. On peut leur donner un nom mais cela ne sert que pour l'affichage.

let r1 = Symbol();
let r2 = Symbol("test");

//On peut ensuite les utiliser dans des propriétés d'objets litérals.

let te1 =
{
    [r1] : "The only one"
};

document.write(te1[r1]);

Les promesses ES6

Les promesses permettent de faciliter le travail en mode asynchrone avec des callbacks.

Javascript utilise beaucoup les callbacks car tout est asynchrone en Javascript.

On va créer un objet de type Promise. Dans le constructeur on spécifie une fonction avec deux paramètre resolve et reject. La fonction resolve sera appelé si l’appel asynchrone s’est bien déroulé reject sera appelé dans le cas contraire.

On va donc définir les callbacks au niveau de l’objet Promise et non plus au niveau de la fonction asynchrone comme avant.

On définit les fonction resolve et reject avec la méthode then de l’objet Promise.

const une promesse = new Promise((resolve, reject) => {
appel de la fonction asynchrone (exemple appel réseau, timer, chargement image)
if (tache bien effectuée)
    resolve(); 
else
    reject() 
});

Ensuite il suffit d’attacher les méthodes resolve et reject avec la méthode then.

Promise.prototype.then(onFulfilled, onRejected)

Exemple de création et d'utilisation d'une promesse

const p = new Promise(function(resolve, reject)
{
    const xhr = new XMLHttpRequest();
    xhr.open("GET", "test");

    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send("exemple");
    //On envoie un message Get asynchrone sur le réseau. Suivant la réponse on appelle resolve ou reject.
    //Si vous lancez l'exemple en local vous aurez toujours une erreur (l'exemple n'est pas fonctionnel) et la fonction reject sera toujours appelé.
});
var fonctionOK = function(texte : string)
{
    document.write("OK");
}
var fonctionKO = function(texte : string)
{
    document.write("KO");
}

p.then(fonctionOK, fonctionKO);
//La fonction then définit les fonction resolve et reject et retourne une promesse.

Les promesses permettent d’augmenter la lisibilité de vos programmes car vous allez chaîner les appels asynchrones avec then, vous n’aurez plus une callback qui appelle une callback. En contrepartie il faut promessifier toutes vos fonctions asynchrones.

Promessifions l’évenement load de la page (exemple de StackOverflow).

function load(){
    return new Promise(function(resolve,reject){
    window.onload = resolve;
    });
}

Il existe des bibliothèques qui le font pour vous automatiquement.

Apparemment aucune fonction Javascript ne retourne une promesse. Vous devrez les faire par vous-même. Il est assez étrange que les concepteurs ajoutent les promesses au langage mais ne les utilisent pas dans les fonctions de base.