TypeScript 5.0 est sorti

Par:
fredericmazue

lun, 20/03/2023 - 15:44

Microsoft a annoncé la sortie de TypeScript 5.0. Selon Microsoft cette nouvelle version du langage le rend plus petit, plus simple et plus rapide. Les deux nouveautés qui nous semblent être les plus importantes sont les décorateurs d'ECMAScript et les paramètres de type const.

Les décorateurs

Les décorateurs sont des fonctions appelées sur des classes, des éléments de classe ou d'autres formes de syntaxe JavaScript. Les décorateurs ont trois capacités principales :

  1. Ils peuvent remplacer la valeur qui est décorée par une valeur correspondante qui a la même sémantique. 
  2. Ils peuvent donner accès à la valeur qui est décorée via des fonctions d'accès qu'ils peuvent ensuite choisir de partager.
  3. Ils peuvent initialiser la valeur qui est décorée, en exécutant du code supplémentaire après que la valeur a été entièrement définie. Dans les cas où la valeur est un membre de la classe, l'initialisation se produit une fois par instance.

Essentiellement, les décorateurs peuvent être utilisés pour métaprogrammer et ajouter des fonctionnalités à une valeur, sans modifier fondamentalement son comportement externe.

Microsoft donne cet exemple :

class Person {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ray");
p.greet();

Supposons qu'à des fins de débogage, on modifie la classe comme ceci :

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log("LOG: Entering method.");
        console.log(`Hello, my name is ${this.name}.`);
        console.log("LOG: Exiting method.")
    }
}

C'est quelque chose qu'un développeur fait fréquemment, et qu'il fait et refait avec d'autres méthodes au cours de son travail. Un décorateur va simplifier les choses. On commence par écrire une fonction de remplacement de caractère générique qui sera amenée à remplacer les méthodes dont on veut suivre l'exécution :

function loggedMethod(originalMethod: any, _context: any) {

    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }

    return replacementMethod;
}

Maintenant loggedMethod peut par exemple décorer la méthode greet de notre classe :

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ray");
p.greet();

// Output:
//   LOG: Entering method.
//   Hello, my name is Ray.
//   LOG: Exiting method.

Ainsi la décoration @loggedMethod a remplacé la méthode greet par la fonction loggedMethod

L'implémentation des décorateurs a évolué depuis TypeScript 5.0 bêta. Ainsi les décorateurs peuvent maintenant avant ou après export et export default.

Les paramètres de type const

Lors de la déduction du type d'un objet, TypeScript choisit généralement un type censé être général. Par exemple, dans l'exemple ci-dessous, le type inféré de names est string[]:

type HasNames = { readonly names: string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}

// Inferred type: string[]
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

Cependant, selon ce que la fonction getNamesExactly fait exactement et comment elle est destinée à être utilisée, il peut souvent arriver qu'un type plus spécifique soit souhaité.

Jusqu'à présent, les auteurs d'API devaient généralement recommander d'ajouter as const à certains endroits pour obtenir l'inférence souhaitée :

// The type we wanted:
//    readonly ["Alice", "Bob", "Eve"]
// The type we got:
//    string[]
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

// Correctly gets what we wanted:
//    readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

Ce qui est fastidieux et peut entraîner des oublis. Dans TypeScript 5.0, vous pouvez désormais ajouter un modificateur const à une déclaration de paramètre de type pour que l'inférence const-like soit la valeur par défaut :

type HasNames = { names: readonly string[] };
function getNamesExactly<const T extends HasNames>(arg: T): T["names"]
//                       ^^^^^
    return arg.names;
}

// Inferred type: readonly ["Alice", "Bob", "Eve"]
// Note: Didn't need to write 'as const' here
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });