IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

TypeScript Discussion :

Microsoft annonce la disponibilité de TypeScript 5.2 Beta.


Sujet :

TypeScript

  1. #1
    Expert éminent sénior

    Femme Profil pro
    Rédacteur Web
    Inscrit en
    Mars 2020
    Messages
    838
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : Cameroun

    Informations professionnelles :
    Activité : Rédacteur Web

    Informations forums :
    Inscription : Mars 2020
    Messages : 838
    Points : 58 035
    Points
    58 035
    Par défaut Microsoft annonce la disponibilité de TypeScript 5.2 Beta.
    Microsoft annonce la disponibilité de TypeScript 5.2 Beta
    et présente une liste rapide des nouveautés de TypeScript 5.2

    Microsoft est heureux d'annoncer la disponibilité de TypeScript 5.2 Beta. Pour commencer à utiliser la version bêta, vous pouvez l'obtenir via NuGet, ou via npm avec la commande suivante :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    npm install -D typescript@beta
    Voici une liste rapide des nouveautés de TypeScript 5.2 !
    • Utilisation des déclarations et gestion explicite des ressources
    • Métadonnées des décorateurs
    • Éléments "Tuple" nommés et anonymes
    • Utilisation plus facile des méthodes pour les Unions de tableaux
    • Compléments de virgule pour les membres d'objets
    • Refonte des variables en ligne
    • Changements de rupture et corrections d'erreurs


    Nom : Screenshot_2023-07-05 Announcing TypeScript 5 2 Beta – Recherche Google.png
Affichages : 4875
Taille : 8,4 Ko

    using des déclarations et de la gestion explicite des ressources

    TypeScript 5.2 prend en charge la prochaine fonctionnalité de gestion explicite des ressources (Explicit Resource Management) de l'ECMAScript. Explorons quelques-unes des motivations et comprenons ce que cette fonctionnalité nous apporte.

    Il est fréquent de devoir effectuer une sorte de "nettoyage" après la création d'un objet. Par exemple, vous pouvez avoir besoin de fermer des connexions réseau, de supprimer des fichiers temporaires ou simplement de libérer de la mémoire.

    Imaginons une fonction qui crée un fichier temporaire, y lit et y écrit pour diverses opérations, puis le ferme et le supprime.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import * as fs from "fs";
     
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        // use file...
     
        // Close the file and delete it.
        fs.closeSync(file);
        fs.unlinkSync(path);
    }
    C'est très bien, mais que se passe-t-il si nous devons effectuer une sortie anticipée ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        // use file...
        if (someCondition()) {
            // do some more work...
     
            // Close the file and delete it.
            fs.closeSync(file);
            fs.unlinkSync(path);
            return;
        }
     
        // Close the file and delete it.
        fs.closeSync(file);
        fs.unlinkSync(path);
    }
    Nous commençons à voir des duplications de nettoyage qui peuvent être facilement oubliées. Nous ne sommes pas non plus assurés de fermer et de supprimer le fichier en cas d'erreur. Cela pourrait être résolu en enveloppant le tout dans un bloc try/finally.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        try {
            // use file...
     
            if (someCondition()) {
                // do some more work...
                return;
            }
        }
        finally {
            // Close the file and delete it.
            fs.closeSync(file);
            fs.unlinkSync(path);
        }
    }
    Bien que cette méthode soit plus robuste, elle a ajouté pas mal de "bruit" à notre code. Il y a également d'autres obstacles que nous pouvons rencontrer si nous commençons à ajouter plus de logique de nettoyage à notre bloc finally - par exemple, des exceptions empêchant d'autres ressources d'être éliminées. C'est ce que la proposition de gestion explicite des ressources vise à résoudre. L'idée principale de la proposition est de soutenir l'élimination des ressources - ce travail de nettoyage que nous essayons de gérer - comme une idée de première classe en JavaScript.

    Cela commence par l'ajout d'un nouveau Symbol intégré appelé Symbol.dispose, et nous pouvons créer des objets avec des méthodes nommées par Symbol.dispose. Pour plus de commodité, TypeScript définit un nouveau type global appelé Disposable qui décrit ces objets.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class TempFile implements Disposable {
        #path: string;
        #handle: number;
     
        constructor(path: string) {
            this.#path = path;
            this.#handle = fs.openSync(path, "w+");
        }
     
        // other methods
     
        [Symbol.dispose]() {
            // Close the file and delete it.
            fs.closeSync(this.#handle);
            fs.unlinkSync(this.#path);
        }
    }
    Plus tard, nous pourrons appeler ces méthodes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export function doSomeWork() {
        const file = new TempFile(".some_temp_file");
     
        try {
            // ...
        }
        finally {
            file[Symbol.dispose]();
        }
    }
    Déplacer la logique de nettoyage dans TempFile lui-même ne nous apporte pas grand-chose ; nous avons essentiellement déplacé tout le travail de nettoyage du bloc finally dans une méthode, ce qui a toujours été possible. Mais le fait d'avoir un "nom" bien connu pour cette méthode signifie que JavaScript peut construire d'autres fonctionnalités au-dessus d'elle.

    Cela nous amène à la première étoile de la fonctionnalité : les déclarations using ! using est un nouveau mot-clé qui nous permet de déclarer de nouvelles liaisons fixes, un peu comme const. La principale différence est que les variables déclarées avec using voient leur méthode Symbol.dispose appelée à la fin de la portée !

    Nous aurions donc pu simplement écrire notre code comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export function doSomeWork() {
        using file = new TempFile(".some_temp_file");
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
    }
    Regardez : pas de blocs try/finally ! Du moins, pas à notre connaissance. D'un point de vue fonctionnel, c'est exactement ce que les déclarations font pour nous, mais nous n'avons pas à nous en préoccuper.

    Vous connaissez peut-être les déclarations en C#, les instructions en Python ou les déclarations try-with-resource en Java. Ces déclarations sont toutes similaires au nouveau mot-clé using de JavaScript et fournissent un moyen explicite similaire d'effectuer un "nettoyage" d'un objet à la fin d'un champ d'application.

    Les déclarations using effectuent ce nettoyage à la toute fin de la portée qu'elles contiennent ou juste avant un "retour anticipé" tel qu'un return ou un thrown error. Elles se débarrassent également de l'objet dans l'ordre premier entré-dernier sorti, comme une pile.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    function loggy(id: string): Disposable {
        console.log(`Creating ${id}`);
     
        return {
            [Symbol.dispose]() {
                console.log(`Disposing ${id}`);
            }
        }
    }
     
    function func() {
        using a = loggy("a");
        using b = loggy("b");
        {
            using c = loggy("c");
            using d = loggy("d");
        }
        using e = loggy("e");
        return;
     
        // Unreachable.
        // Never created, never disposed.
        using f = loggy("f");
    }
     
    f();
    // Creating a
    // Creating b
    // Creating c
    // Creating d
    // Disposing d
    // Disposing c
    // Creating e
    // Disposing e
    // Disposing b
    // Disposing a
    Les déclarations using sont censées être résistantes aux exceptions ; si une erreur est lancée, elle est relancée après l'élimination. D'un autre côté, le corps de votre fonction peut s'exécuter comme prévu, mais le Symbol.dispose peut être rejeté. Dans ce cas, cette exception est également relancée.

    Mais que se passe-t-il si la logique avant et pendant l'élimination génère une erreur ? Pour ces cas, SuppressedError a été introduit comme nouveau sous-type de Error. Il comporte une propriété suppressed qui contient la dernière erreur levée, et une propriété error pour l'erreur la plus récente.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class ErrorA extends Error {
        name = "ErrorA";
    }
    class ErrorB extends Error {
        name = "ErrorB";
    }
     
    function throwy(id: string) {
        return {
            [Symbol.dispose]() {
                throw new ErrorA(`Error from ${id}`);
            }
        };
    }
     
    function func() {
        using a = throwy("a");
        throw new ErrorB("oops!")
    }
     
    try {
        func();
    }
    catch (e: any) {
        console.log(e.name); // SuppressedError
        console.log(e.message); // An error was suppressed during disposal.
     
        console.log(e.error.name); // ErrorA
        console.log(e.error.message); // Error from a
     
        console.log(e.suppressed.name); // ErrorB
        console.log(e.suppressed.message); // oops!
    }
    Vous avez peut-être remarqué que nous utilisons des méthodes synchrones dans ces exemples. Cependant, une grande partie de l'élimination des ressources implique des opérations asynchrones, et nous devons attendre qu'elles soient terminées avant de continuer à exécuter tout autre code.

    C'est pourquoi il existe également un nouveau Symbol.asyncDispose, qui nous amène à la prochaine vedette du spectacle — les déclarations await using. Celles-ci sont similaires aux déclarations d'utilisation, mais la clé est qu'elles recherchent la disposition qui doit await. Elles utilisent une méthode différente nommée Symbol.asyncDispose, bien qu'elles puissent également opérer sur tout ce qui possède un Symbol.dispose. Par commodité, TypeScript introduit également un type global appelé AsyncDisposable qui décrit tout objet doté d'une méthode d'élimination asynchrone.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    async function doWork() {
        // Do fake work for half a second.
        await new Promise(resolve => setTimeout(resolve, 500));
    }
     
    function loggy(id: string): AsyncDisposable {
        console.log(`Constructing ${id}`);
        return {
            async [Symbol.asyncDispose]() {
                console.log(`Disposing (async) ${id}`);
                await doWork();
            },
        }
    }
     
    async function func() {
        await using a = loggy("a");
        await using b = loggy("b");
        {
            await using c = loggy("c");
            await using d = loggy("d");
        }
        await using e = loggy("e");
        return;
     
        // Unreachable.
        // Never created, never disposed.
        await using f = loggy("f");
    }
     
    f();
    // Constructing a
    // Constructing b
    // Constructing c
    // Constructing d
    // Disposing (async) d
    // Disposing (async) c
    // Constructing e
    // Disposing (async) e
    // Disposing (async) b
    // Disposing (async) a
    Définir les types en termes de Disposable et AsyncDisposable peut rendre votre code beaucoup plus facile à travailler si vous attendez des autres qu'ils fassent de la logique de destruction de manière cohérente. En fait, il existe de nombreux types existants dans la nature qui ont une méthode dispose( ) ou close( ). Par exemple, les API de Visual Studio Code définissent même leur propre interface Disposable. Les API dans le navigateur et dans les moteurs d'exécution comme Node.js, Deno et Bun peuvent également choisir d'utiliser Symbol.dispose et Symbol.asyncDispose pour les objets qui ont déjà des méthodes de nettoyage, comme les poignées de fichiers, les connexions, et plus encore.

    Peut-être que tout cela semble excellent pour les bibliothèques, mais un peu lourd pour vos scénarios. Si vous faites beaucoup de nettoyage ad-hoc, la création d'un nouveau type pourrait introduire beaucoup de sur-abstraction et des questions sur les meilleures pratiques. Reprenons l'exemple de TempFile.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class TempFile implements Disposable {
        #path: string;
        #handle: number;
     
        constructor(path: string) {
            this.#path = path;
            this.#handle = fs.openSync(path, "w+");
        }
     
        // other methods
     
        [Symbol.dispose]() {
            // Close the file and delete it.
            fs.closeSync(this.#handle);
            fs.unlinkSync(this.#path);
        }
    }
     
    export function doSomeWork() {
        using file = new TempFile(".some_temp_file");
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
    }
    Tout ce que nous voulions, c'était nous souvenir d'appeler deux fonctions - mais était-ce la meilleure façon de l'écrire ? Devrions-nous appeler openSync dans le constructeur, créer une méthode open( ), ou passer la poignée nous-mêmes ? Devrions-nous exposer une méthode pour chaque opération possible que nous devons effectuer, ou devrions-nous simplement rendre les propriétés publiques ?

    Cela nous amène aux dernières étoiles de la fonctionnalité : DisposableStack et AsyncDisposableStack. Ces objets sont utiles pour effectuer à la fois un nettoyage ponctuel et des quantités arbitraires de nettoyage. Un DisposableStack est un objet qui possède plusieurs méthodes pour garder une trace des objets Disposable, et qui peut recevoir des fonctions pour effectuer un travail de nettoyage arbitraire. Nous pouvons également les assigner à des variables using parce que - tenez-vous bien - elles sont également Disposable ! Voici donc comment nous aurions pu écrire l'exemple original.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        using cleanup = new DisposableStack();
        cleanup.defer(() => {
            fs.closeSync(file);
            fs.unlinkSync(path);
        });
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
     
        // ...
    }
    Ici, la méthode defer( ) prend juste un callback, et ce callback sera exécuté une fois que le cleanup aura été éliminé. Généralement, defer (et d'autres méthodes DisposableStack comme use et adopt) doit être appelé immédiatement après la création d'une ressource. Comme son nom l'indique, DisposableStack se débarrasse de tout ce qu'elle suit comme une pile, dans l'ordre "premier entré - dernier sorti", donc difer immédiatement après la création d'une valeur permet d'éviter les problèmes de dépendance. AsyncDisposable fonctionne de manière similaire, mais peut garder la trace des fonctions async et des AsyncDisposables, et est lui-même un AsyncDisposable.

    La méthode defer est similaire à bien des égards au mot-clé defer dans Go, Swift, Zig, Odin, et d'autres, où les conventions devraient être similaires.

    Cette fonctionnalité étant très récente, la plupart des systèmes d'exécution ne la supporteront pas nativement. Pour l'utiliser, vous aurez besoin de polyfills pour les éléments suivants :

    • Symbol.dispose
    • Symbol.asyncDispose
    • DisposableStack
    • AsyncDisposableStack
    • SuppressedError


    Cependant, si tout ce qui vous intéresse est using et await using, vous devriez pouvoir vous en sortir en ne remplissant que les symbols intégrés. Quelque chose d'aussi simple que ce qui suit devrait fonctionner dans la plupart des cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Symbol.dispose ??= Symbol("Symbol.dispose");
    Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");
    Vous devrez également définir votre compilation target à es2022 ou moins, et configurer votre lib pour inclure "esnext" ou "esnext.disposable".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    {
        "compilerOptions": {
            "target": "es2022",
            "lib": ["es2022", "esnext.disposable", "dom"]
        }
    }
    Métadonnées des décorateurs

    TypeScript 5.2 met en œuvre une fonctionnalité ECMAScript à venir appelée métadonnées de décorateur.

    L'idée principale de cette fonctionnalité est de permettre aux décorateurs de créer et de consommer facilement des métadonnées sur n'importe quelle classe sur laquelle ils sont utilisés ou à l'intérieur de celle-ci.

    Chaque fois que des fonctions de décorateur sont utilisées, elles ont désormais accès à une nouvelle propriété de metadata sur leur objet de contexte. La propriété metadata contient un simple objet. Comme JavaScript nous permet d'ajouter des propriétés de manière arbitraire, il peut être utilisé comme un dictionnaire mis à jour par chaque décorateur. Alternativement, puisque chaque objet de metadata sera identique pour chaque portion décorée d'une classe, il peut être utilisé comme une clé dans une Map. Une fois que tous les décorateurs sur ou dans une classe ont été exécutés, cet objet peut être accédé sur la classe via Symbol.metadata.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface Context {
        name: string;
        metadata: Record<PropertyKey, unknown>;
    }
     
    function setMetadata(_target: any, context: Context) {
        context.metadata[context.name] = true;
    }
     
    class SomeClass {
        @setMetadata
        foo = 123;
     
        @setMetadata
        accessor bar = "hello!";
     
        @setMetadata
        baz() { }
    }
     
    const ourMetadata = SomeClass[Symbol.metadata];
     
    console.log(JSON.stringify(ourMetadata));
    // { "bar": true, "baz": true, "foo": true }
    Cela peut être utile dans un certain nombre de scénarios différents. Les métadonnées peuvent être attachées à de nombreux usages tels que le débogage, la sérialisation ou l'injection de dépendances avec des décorateurs. Puisque les objets de métadonnées sont créés par classe décorée, les frameworks peuvent soit les utiliser en privé comme clés dans une Map ou une WeakMap, soit leur ajouter des propriétés si nécessaire.

    Par exemple, disons que nous voulions utiliser les décorateurs pour garder une trace des propriétés et des accesseurs sérialisables lorsque nous utilisons JSON.stringify de la manière suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    mport { serialize, jsonify } from "./serializer";
     
    class Person {
        firstName: string;
        lastName: string;
     
        @serialize
        age: number
     
        @serialize
        get fullName() {
            return `${this.firstName} ${this.lastName}`;
        }
     
        toJSON() {
            return jsonify(this)
        }
     
        constructor(firstName: string, lastName: string, age: number) {
            // ...
        }
    }
    Ici, l'intention est que seuls age et fullName soient sérialisés parce qu'ils sont marqués avec le décorateur @serialize. Nous définissons une méthode toJSON à cette fin, mais elle appelle simplement jsonify qui utilise les métadonnées créées par @serialize.

    Voici un exemple de définition du module ./serialize.ts :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const serializables = Symbol();
     
    type Context =
        | ClassAccessorDecoratorContext
        | ClassGetterDecoratorContext
        | ClassFieldDecoratorContext
        ;
     
    export function serialize(_target: any, context: Context): void {
        if (context.static || context.private) {
            throw new Error("Can only serialize public instance members.")
        }
        if (typeof context.name === "symbol") {
            throw new Error("Cannot serialize symbol-named properties.");
        }
     
        const propNames =
            (context.metadata[serializables] as string[] | undefined) ??= [];
        propNames.push(context.name);
    }
     
    export function jsonify(instance: object): string {
        const metadata = instance.constructor[Symbol.metadata];
        const propNames = metadata?.[serializables] as string[] | undefined;
        if (!propNames) {
            throw new Error("No members marked with @serialize.");
        }
     
        const pairStrings = propNames.map(key => {
            const strKey = JSON.stringify(key);
            const strValue = JSON.stringify((instance as any)[key]);
            return `${strKey}: ${strValue}`;
        });
     
        return `{ ${pairStrings.join(", ")} }`;
    }
    Ce module possède un symbol local appelé serializables pour stocker et récupérer les noms des propriétés marquées @serializable. Il stocke une liste de ces noms de propriétés dans les métadonnées à chaque invocation de @serializable. Lorsque jsonify est appelé, la liste des propriétés est extraite des métadonnées et utilisée pour récupérer les valeurs réelles de l'instance, puis pour sérialiser ces noms et ces valeurs.

    L'utilisation d'un symbole rend techniquement ces données accessibles à d'autres personnes. Une alternative pourrait être d'utiliser un WeakMap en utilisant l'objet de métadonnées comme clé. Cela permet de garder les données privées et d'utiliser moins d'assertions de type dans ce cas, mais c'est autrement similaire.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    const serializables = new WeakMap<object, string[]>();
     
    type Context =
        | ClassAccessorDecoratorContext
        | ClassGetterDecoratorContext
        | ClassFieldDecoratorContext
        ;
     
    export function serialize(_target: any, context: Context): void {
        if (context.static || context.private) {
            throw new Error("Can only serialize public instance members.")
        }
        if (typeof context.name !== "string") {
            throw new Error("Can only serialize string properties.");
        }
     
        const propNames = serializables.get(context.metadata);
        if (propNames === undefined) {
            serializables.set(context.metadata, propNames = []);
        }
        propNames.push(context.name);
    }
     
    export function jsonify(instance: object): string {
        const metadata = instance.constructor[Symbol.metadata];
        const propNames = metadata && serializables.get(metadata);
        if (!propNames) {
            throw new Error("No members marked with @serialize.");
        }
        const pairStrings = propNames.map(key => {
            const strKey = JSON.stringify(key);
            const strValue = JSON.stringify((instance as any)[key]);
            return `${strKey}: ${strValue}`;
        });
     
        return `{ ${pairStrings.join(", ")} }`;
    }
    Il est à noter que ces implémentations ne gèrent pas la sous-classe et l'héritage. C'est un exercice qui vous est laissé (et vous pourriez trouver que c'est plus facile dans une version du fichier que dans l'autre !)

    Cette fonctionnalité étant encore récente, la plupart des systèmes d'exécution ne la supporteront pas nativement. Pour l'utiliser, vous aurez besoin d'un polyfill pour Symbol.metadata. Quelque chose d'aussi simple que ce qui suit devrait fonctionner dans la plupart des cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Symbol.metadata ??= Symbol("Symbol.metadata");
    Vous devrez également définir votre compilation target à es2022 ou moins, et configurer votre lib pour inclure "esnext" ou "esnext.decorators".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    {
        "compilerOptions": {
            "target": "es2022",
            "lib": ["es2022", "esnext.decorators", "dom"]
        }
    }
    Éléments de tuple nommés et anonymes

    Les types de tuple prennent en charge des étiquettes ou des noms facultatifs pour chaque élément.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    type Pair<T> = [first: T, second: T];
    Ces étiquettes ne modifient pas ce que vous êtes autorisé à faire avec eux - elles sont uniquement destinées à faciliter la lisibilité et l'outillage.

    Cependant, TypeScript avait auparavant une règle selon laquelle les tuples ne pouvaient pas mélanger des éléments étiquetés et non étiquetés. En d'autres termes, soit aucun élément ne pouvait avoir d'étiquette dans un tuple, soit tous les éléments devaient en avoir une.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //  fine - no labels
    type Pair1<T> = [T, T];
     
    //  fine - all fully labeled
    type Pair2<T> = [first: T, second: T];
     
    //  previously an error
    type Pair3<T> = [first: T, T];
    //                         ~
    // Tuple members must all have names
    // or all not have names.
    Cela pourrait être gênant pour les éléments rest, pour lesquels nous serions obligés d'ajouter un libellé tel que rest ou tail.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    //  previously an error
    type TwoOrMore_A<T> = [first: T, second: T, ...T[]];
    //                                          ~~~~~~
    // Tuple members must all have names
    // or all not have names.
     
    // 
    type TwoOrMore_B<T> = [first: T, second: T, rest: ...T[]];
    Cela signifie également que cette restriction doit être appliquée en interne dans le système de types, ce qui signifie que TypeScript perdrait des étiquettes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    type HasLabels = [a: string, b: string];
    type HasNoLabels = [number, number];
    type Merged = [...HasNoLabels, ...HasLabels];
    //   ^ [number, number, string, string]
    //
    //     'a' and 'b' were lost in 'Merged'
    Dans TypeScript 5.2, la restriction "tout ou rien" sur les étiquettes de tuple a été levée. Le langage peut désormais également préserver les étiquettes lors de la propagation dans un tuple non étiqueté.

    Utilisation plus facile des méthodes pour les unions de tableaux

    Dans les versions précédentes de TypeScript, l'appel d'une méthode sur une union de tableaux pouvait s'avérer pénible.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    declare let array: string[] | number[];
     
    array.filter(x => !!x);
    //    ~~~~~~ error!
    // This expression is not callable.
    //   Each member of the union type '...' has signatures,
    //   but none of those signatures are compatible
    //   with each other.
    Dans cet exemple, TypeScript essaierait de voir si chaque version de filter est compatible avec string[ ] et number[ ]. Sans stratégie cohérente, TypeScript a jeté ses mains en l'air et a dit "je ne peux pas le faire fonctionner".

    Dans TypeScript 5.2, avant d'abandonner dans ces cas, les unions de tableaux sont traitées comme un cas spécial. Un nouveau type de tableau est construit à partir du type d'élément de chaque membre, puis la méthode est invoquée sur celui-ci.

    Dans l'exemple ci-dessus, string[ ] | number[ ] est transformé en (string | number)[ ] (ou Array<string | number>), et filter est invoqué sur ce type. Il y a un léger inconvénient : filter produira un Array<string | number> au lieu d'un string[] | number[] ; mais pour une valeur fraîchement produite, il y a moins de risque que quelque chose "tourne mal".

    Cela signifie que de nombreuses méthodes telles que filter, find, some, every et reduce devraient toutes pouvoir être invoquées sur des unions de tableaux dans des cas où elles ne l'étaient pas auparavant.

    Compléments à la virgule pour les membres d'un objet

    Il peut être facile d'oublier d'ajouter une virgule lors de l'ajout d'une nouvelle propriété à un objet. Auparavant, si vous oubliiez une virgule et que vous demandiez l'auto-complétion, TypeScript donnait des résultats de complétion médiocres et sans rapport avec le sujet.

    TypeScript 5.2 fournit désormais de manière élégante des compléments sur les membres d'un objet lorsqu'il manque une virgule. Mais pour éviter de vous infliger une erreur de syntaxe, TypeScript insère automatiquement la virgule manquante.

    Nom : comma-completions-5-2-beta.gif
Affichages : 2077
Taille : 115,1 Ko

    Refactorisation des variables en ligne

    TypeScript 5.2 dispose désormais d'un refactoring permettant d'intégrer le contenu d'une variable dans tous les sites d'utilisation.

    Nom : inline-variable-5-2-beta.gif
Affichages : 2052
Taille : 97,5 Ko

    L'utilisation du refactoring "inline variable" éliminera la variable et remplacera toutes les utilisations de la variable par son initialisateur. Notez que cela peut entraîner l'exécution des effets secondaires de l'initialisateur à un moment différent et autant de fois que la variable a été utilisée.

    Changements de rupture et corrections

    TypeScript s'efforce de ne pas introduire inutilement des ruptures ; cependant, nous devons parfois apporter des corrections et des améliorations afin que le code puisse être mieux analysé

    Changements dans lib.d.tsLes types générés pour le DOM peuvent avoir un impact sur votre base de code.

    labeledElementDeclarations peut contenir des éléments undefined
    Afin de supporter un mélange d'éléments étiquetés et non étiquetés, l'API de TypeScript a légèrement changé. La propriété labeledElementDeclarations de TupleType peut contenir des éléments non définis à chaque position où un élément n'est pas étiqueté.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     interface TupleType {
    -     labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
    +     labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
    module et moduleResolution doivent correspondre aux paramètres récents de Node.js

    Les options --module et --moduleResolution supportent chacune les paramètres node16 et nodenext. Il s'agit en fait de paramètres "Node.js modernes" qui devraient être utilisés dans tout projet Node.js récent. Nous avons constaté que lorsque ces deux options ne s'accordent pas sur l'utilisation des paramètres liés à Node.js, les projets sont effectivement mal configurés.

    Dans TypeScript 5.2, lorsque l'on utilise node16 ou nodenex pour les options --module et --moduleResolution, TypeScript exige désormais que l'autre option ait un paramètre similaire lié à Node.js. Dans les cas où les paramètres divergent, vous obtiendrez probablement un message d'erreur du type.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Option 'moduleResolution' must be set to 'NodeNext' (or left unspecified) when option 'module' is set to 'NodeNext'.
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Option 'module' must be set to 'Node16' when option 'moduleResolution' is set to 'Node16'.
    Ainsi, par exemple, --module esnext --moduleResolution node16 sera rejeté, mais il est préférable d'utiliser --module nodenext seul, ou --module esnext --moduleResolution bundler.

    Vérification cohérente de l'exportation des symboles fusionnés

    Lorsque deux déclarations fusionnent, elles doivent s'accorder sur le fait qu'elles sont toutes deux exportées. En raison d'un bogue, TypeScript a manqué des cas spécifiques dans des contextes ambiants, comme dans les fichiers de déclaration ou les blocs declare module. Par exemple, il n'émettrait pas d'erreur dans un cas comme celui-ci, où replaceInFile est déclaré une fois en tant que fonction exportée, et une fois en tant qu'espace de noms non exporté.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    declare module 'replace-in-file' {
        export function replaceInFile(config: unknown): Promise<unknown[]>;
        export {};
     
        namespace replaceInFile {
            export function sync(config: unknown): unknown[];
      }
    }
    Dans un module ambiant, l'ajout d'un export { ... } ou une construction similaire comme export default ... modifie implicitement si toutes les déclarations sont automatiquement exportées. TypeScript reconnaît maintenant cette sémantique malheureusement déroutante de manière plus cohérente, et émet une erreur sur le fait que toutes les déclarations de replaceInFile doivent être en accord dans leurs modificateurs, et émettra l'erreur suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Individual declarations in merged declaration 'replaceInFile' must be all exported or all local.
    Quelles sont les prochaines étapes ?

    À ce stade, TypeScript 5.2 est ce que nous appellerions "stable en termes de fonctionnalités". TypeScript 5.2 se concentrera sur les corrections de bogues, le polissage et certaines fonctionnalités d'édition à faible risque. Une release candidate sera disponible dans un peu plus d'un mois, suivie d'une version stable peu après. Si vous souhaitez planifier la sortie de cette version, n'oubliez pas de garder un œil sur le plan d'itération qui indique les dates de sortie et bien d'autres choses encore.

    Remarque : bien que la version bêta soit un excellent moyen d'essayer la prochaine version de TypeScript, vous pouvez également essayer une version nocturne pour obtenir la version la plus récente de TypeScript 5.2 jusqu'à la publication de la release candidate. Les nightlies sont bien testées et peuvent même être testées uniquement dans votre éditeur.

    Joyeux hacking !

    - Daniel Rosenwasser et l'équipe TypeScript

    Source : Microsoft

    Et vous ?

    Quel est votre avis sur le sujet ?

    Que pensez-vous des fonctionnalités proposées par cette version de TypeScript ?

    Voir aussi :

    Microsoft annonce la disponibilité de TypeScript 5.0. Et présente les principaux changements notables depuis la publication de la version bêta

    Microsoft annonce la disponibilité de la version 5.1 de TypeScript, et présente les changements depuis les versions bêta et release candidate
    Contribuez au club : Corrections, suggestions, critiques, ... : Contactez le service news et Rédigez des actualités

  2. #2
    Membre expérimenté
    Homme Profil pro
    bricoleur par les mots
    Inscrit en
    Avril 2015
    Messages
    715
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 79
    Localisation : France, Seine Maritime (Haute Normandie)

    Informations professionnelles :
    Activité : bricoleur par les mots
    Secteur : Distribution

    Informations forums :
    Inscription : Avril 2015
    Messages : 715
    Points : 1 600
    Points
    1 600
    Par défaut
    Quel est votre avis sur le sujet ?
    apparemment il faut avoir beaucoup d'imagination.
    Plus vite encore plus vite toujours plus vite.

  3. #3
    Expert éminent sénior

    Femme Profil pro
    Rédacteur Web
    Inscrit en
    Mars 2020
    Messages
    838
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : Cameroun

    Informations professionnelles :
    Activité : Rédacteur Web

    Informations forums :
    Inscription : Mars 2020
    Messages : 838
    Points : 58 035
    Points
    58 035
    Par défaut Microsoft annonce la Release Candidate de TypeScript 5.2.
    Microsoft annonce la Release Candidate de TypeScript 5.2
    et présente une liste rapide des nouveautés de TypeScript 5.2

    Microsoft est heureux d'annoncer la Release Candidate de TypeScript 5.2 ! D'ici la sortie de la version stable de TypeScript 5.2, Microsoft ne prévoit pas d'autres changements que des corrections de bogues critiques.

    Nom : Screenshot_2023-08-10 Microsoft annonce la disponibilité de TypeScript 5 2 Beta et présente une .png
Affichages : 6824
Taille : 8,4 Ko

    Pour commencer à utiliser la RC, vous pouvez l'obtenir via NuGet, ou via npm avec la commande suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    npm install -D typescript@rc

    Voici une liste rapide des nouveautés de TypeScript 5.2 !

    • Utilisation des déclarations et gestion explicite des ressources
    • Métadonnées des décorateurs
    • Éléments de tuple nommés et anonymes
    • Utilisation plus facile des méthodes pour les Unions de tableaux
    • Chemins d'importation réservés aux types avec extensions de fichiers d'implémentation TypeScript
    • Compléments de virgule pour les membres d'objets
    • Refonte des variables en ligne
    • Vérifications optimisées pour la compatibilité des types en cours
    • Changements de rupture et corrections de correction


    Quoi de neuf depuis la version bêta ?

    Depuis la version bêta, Micorsoft a ajouté une optimisation de la vérification des types et rendu possible la référence aux chemins des fichiers d'implémentation TypeScript dans les importations de types uniquement.

    Using Declarations and Explicit Resource Management (utilisation des déclarations et de la gestion explicite des ressources)

    TypeScript 5.2 prend en charge la prochaine fonctionnalité de gestion explicite des ressources (Explicit Resource Management) de l'ECMAScript. Explorons quelques-unes des motivations et comprenons ce que cette fonctionnalité nous apporte.

    Il est fréquent de devoir effectuer une sorte de "nettoyage" après la création d'un objet. Par exemple, vous pouvez avoir besoin de fermer des connexions réseau, de supprimer des fichiers temporaires ou simplement de libérer de la mémoire.

    Imaginons une fonction qui crée un fichier temporaire, y lit et y écrit pour diverses opérations, puis le ferme et le supprime.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import * as fs from "fs";
     
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        // use file...
     
        // Close the file and delete it.
        fs.closeSync(file);
        fs.unlinkSync(path);
    }
    C'est très bien, mais que se passe-t-il si une sortie anticipée doit être effectuée ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        // use file...
        if (someCondition()) {
            // do some more work...
     
            // Close the file and delete it.
            fs.closeSync(file);
            fs.unlinkSync(path);
            return;
        }
     
        // Close the file and delete it.
        fs.closeSync(file);
        fs.unlinkSync(path);
    }
    Microsoft commence à voir des duplications de nettoyage qui peuvent être facilement oubliées. Microsoft n’est pas non plus assuré de fermer et de supprimer le fichier en cas d'erreur. Cela pourrait être résolu en enveloppant le tout dans un bloc try/finally.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        try {
            // use file...
     
            if (someCondition()) {
                // do some more work...
                return;
            }
        }
        finally {
            // Close the file and delete it.
            fs.closeSync(file);
            fs.unlinkSync(path);
        }
    }
    Bien que cette méthode soit plus robuste, elle a ajouté pas mal de "bruit" au code. Il y a également d'autres obstacles que Microsoft peut rencontrer si Microsoft commence à ajouter plus de logique de nettoyage à son bloc finally - par exemple, des exceptions empêchant d'autres ressources d'être éliminées. C'est ce que la proposition de gestion explicite des ressources vise à résoudre. L'idée principale de la proposition est de soutenir l'élimination des ressources - ce travail de nettoyage que nous essayons de gérer - comme une idée de première classe en JavaScript.

    Cela commence par l'ajout d'un nouveau symbole intégré appelé Symbol.dispose, et Microsoft peut créer des objets avec des méthodes nommées par Symbol.dispose. Pour plus de commodité, TypeScript définit un nouveau type global appelé Disposable qui décrit ces objets.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class TempFile implements Disposable {
        #path: string;
        #handle: number;
     
        constructor(path: string) {
            this.#path = path;
            this.#handle = fs.openSync(path, "w+");
        }
     
        // other methods
     
        [Symbol.dispose]() {
            // Close the file and delete it.
            fs.closeSync(this.#handle);
            fs.unlinkSync(this.#path);
        }
    }
    Plus tard ces méthodes pourrons être appelées

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export function doSomeWork() {
        const file = new TempFile(".some_temp_file");
     
        try {
            // ...
        }
        finally {
            file[Symbol.dispose]();
        }
    }
    Déplacer la logique de nettoyage dans TempFile lui-même n’apporte pas grand-chose ; Microsoft a essentiellement déplacé tout le travail de nettoyage du bloc finally dans une méthode, ce qui a toujours été possible. Mais le fait d'avoir un "nom" bien connu pour cette méthode signifie que JavaScript peut construire d'autres fonctionnalités au-dessus d'elle.

    Cela nous amène à la première étoile de la fonctionnalité : les using déclarations ! using est un nouveau mot-clé qui nous permet de déclarer de nouvelles liaisons fixes, un peu comme const. La principale différence est que les variables déclarées avec using voient leur méthode Symbol.dispose appelée à la fin de la portée !

    Nous aurions donc pu simplement écrire notre code comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export function doSomeWork() {
        using file = new TempFile(".some_temp_file");
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
    }
    Regardez : pas de try/finally blocs ! Du moins, pas à la connaissance de Microsoft. D'un point de vue fonctionnel, c'est exactement ce que les using déclarations font pour nous, mais nous n'avons pas à nous en préoccuper.

    Vous connaissez peut-être les using déclarations en C#, les instructions en Python ou les déclarations try-with-resource en Java. Ces déclarations sont toutes similaires au nouveau mot-clé usingde JavaScript et fournissent un moyen explicite similaire d'effectuer un "nettoyage" d'un objet à la fin d'un champ d'application.

    Les using déclarations effectuent ce nettoyage à la toute fin de la portée qu'elles contiennent ou juste avant un "retour anticipé" tel qu'un returnou thrownerror. Elles se débarrassent également des objets dans l'ordre "premier entré-dernier sorti", comme une pile.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    function loggy(id: string): Disposable {
        console.log(`Creating ${id}`);
     
        return {
            [Symbol.dispose]() {
                console.log(`Disposing ${id}`);
            }
        }
    }
     
    function func() {
        using a = loggy("a");
        using b = loggy("b");
        {
            using c = loggy("c");
            using d = loggy("d");
        }
        using e = loggy("e");
        return;
     
        // Unreachable.
        // Never created, never disposed.
        using f = loggy("f");
    }
     
    func();
    // Creating a
    // Creating b
    // Creating c
    // Creating d
    // Disposing d
    // Disposing c
    // Creating e
    // Disposing e
    // Disposing b
    // Disposing a
    Les usingdéclarations sont censées être résistantes aux exceptions ; si une erreur est lancée, elle est relancée après l'élimination. D'un autre côté, le corps de votre fonction peut s'exécuter comme prévu, mais le Symbol.disposepeut être rejeté. Dans ce cas, cette exception est également relancée.

    Mais que se passe-t-il si la logique avant et pendant l'élimination génère une erreur ? Pour ces cas, SuppressedErrora été introduit comme nouveau sous-type Error. Il comporte une propriété suppressedqui contient la dernière erreur levée, et une propriété errorpour l'erreur la plus récente.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class ErrorA extends Error {
        name = "ErrorA";
    }
    class ErrorB extends Error {
        name = "ErrorB";
    }
     
    function throwy(id: string) {
        return {
            [Symbol.dispose]() {
                throw new ErrorA(`Error from ${id}`);
            }
        };
    }
     
    function func() {
        using a = throwy("a");
        throw new ErrorB("oops!")
    }
     
    try {
        func();
    }
    catch (e: any) {
        console.log(e.name); // SuppressedError
        console.log(e.message); // An error was suppressed during disposal.
     
        console.log(e.error.name); // ErrorA
        console.log(e.error.message); // Error from a
     
        console.log(e.suppressed.name); // ErrorB
        console.log(e.suppressed.message); // oops!
    }
    Vous avez peut-être remarqué que des méthodes synchrones sont utilisées dans ces exemples. Cependant, une grande partie de l'élimination des ressources implique des opérations asynchrones, et Microsoft doit attendre qu'elles soient terminées avant de continuer à exécuter tout autre code.

    C'est pourquoi il existe également un nouveau Symbol.asyncDispose, qui amène à la prochaine vedette du spectacle : l'utilisation des déclarations d'attente. Celles-ci sont similaires aux await usingdéclarations, mais la clé est qu'elles recherchent la disposition qui doit êtreawait. Elles utilisent une méthode différente nommée Symbol.asyncDispose, bien qu'elles puissent également opérer sur tout ce qui possède un Symbol.dispose. Par commodité, TypeScript introduit également un type global appelé AsyncDisposable qui décrit tout objet doté d'une méthode d'élimination asynchrone.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    async function doWork() {
        // Do fake work for half a second.
        await new Promise(resolve => setTimeout(resolve, 500));
    }
     
    function loggy(id: string): AsyncDisposable {
        console.log(`Constructing ${id}`);
        return {
            async [Symbol.asyncDispose]() {
                console.log(`Disposing (async) ${id}`);
                await doWork();
            },
        }
    }
     
    async function func() {
        await using a = loggy("a");
        await using b = loggy("b");
        {
            await using c = loggy("c");
            await using d = loggy("d");
        }
        await using e = loggy("e");
        return;
     
        // Unreachable.
        // Never created, never disposed.
        await using f = loggy("f");
    }
     
    func();
    // Constructing a
    // Constructing b
    // Constructing c
    // Constructing d
    // Disposing (async) d
    // Disposing (async) c
    // Constructing e
    // Disposing (async) e
    // Disposing (async) b
    // Disposing (async) a
    Définir les types en termes de Disposableet AsyncDisposablepeut rendre votre code beaucoup plus facile à travailler si vous attendez des autres qu'ils fassent de la logique de destruction de manière cohérente. En fait, il existe de nombreux types existants dans la nature qui ont une méthode dispose() ou close(). Par exemple, les API de Visual Studio Code définissent même leur propre interface Disposable. Les API dans le navigateur et dans les moteurs d'exécution comme Node.js, Deno et Bun peuvent également choisir d'utiliser Symbol.disposeet Symbol.asyncDispose pour les objets qui ont déjà des méthodes de nettoyage, comme les poignées de fichiers, les connexions, et plus encore.

    Peut-être que tout cela semble excellent pour les bibliothèques, mais un peu lourd pour vos scénarios. Si vous faites beaucoup de nettoyage ad-hoc, la création d'un nouveau type pourrait introduire beaucoup de sur-abstraction et des questions sur les meilleures pratiques. Reprenons l'exemple de TempFile.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class TempFile implements Disposable {
        #path: string;
        #handle: number;
     
        constructor(path: string) {
            this.#path = path;
            this.#handle = fs.openSync(path, "w+");
        }
     
        // other methods
     
        [Symbol.dispose]() {
            // Close the file and delete it.
            fs.closeSync(this.#handle);
            fs.unlinkSync(this.#path);
        }
    }
     
    export function doSomeWork() {
        using file = new TempFile(".some_temp_file");
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
    }
    Tout ce que Microsoft voulait, c'était de se souvenir d'appeler deux fonctions - mais était-ce la meilleure façon de l'écrire ? Faudrait-il appeler openSyncdans le constructeur, créer une méthode open ( ), ou passer la poignée nous-mêmes ? Faudrait-il exposer une méthode pour chaque opération possible à effectuer, ou Faudrait-il simplement rendre les propriétés publiques ?

    Cela nous amène aux dernières étoiles de la fonctionnalité : DisposableStacket AsyncDisposableStack. Ces objets sont utiles pour effectuer à la fois un nettoyage ponctuel et des quantités arbitraires de nettoyage. Un DisposableStackest un objet qui possède plusieurs méthodes pour garder une trace des objets Disposable, et qui peut recevoir des fonctions pour effectuer un travail de nettoyage arbitraire. Nous pouvons également les assigner à des usingvariables parce que - tenez-vous bien - elles sont également jetables ! Voici donc comment l'exemple original aurait pu être écrit.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function doSomeWork() {
        const path = ".some_temp_file";
        const file = fs.openSync(path, "w+");
     
        using cleanup = new DisposableStack();
        cleanup.defer(() => {
            fs.closeSync(file);
            fs.unlinkSync(path);
        });
     
        // use file...
     
        if (someCondition()) {
            // do some more work...
            return;
        }
     
        // ...
    }
    Ici, la méthode defer() prend juste un callback, et ce callback sera exécuté une fois que le cleanup aura été éliminé. Généralement, defer(et d'autres méthodes DisposableStackcomme useet adopt) doit être appelé immédiatement après la création d'une ressource. Comme son nom l'indique, DisposableStackse débarrasse de tout ce qu'elle suit comme une pile, dans l'ordre "premier entré - dernier sorti", doncdiffer immédiatement après la création d'une valeur permet d'éviter les problèmes de dépendance. AsyncDisposablefonctionne de manière similaire, mais peut garder la trace desasync fonctions et des AsyncDisposables, et est lui-même un AsyncDisposable.

    La méthode deferest similaire à bien des égards au mot-clé defer dans Go, Swift, Zig, Odin, et d'autres, où les conventions devraient être similaires.

    Cette fonctionnalité étant très récente, la plupart des systèmes d'exécution ne la supporteront pas nativement. Pour l'utiliser, vous aurez besoin de polyfills pour les éléments suivants :

    • Symbol.dispose
    • Symbol.asyncDispose
    • Pile jetable
    • AsyncDisposableStack
    • SuppressedError


    Cependant, si tout ce qui vous intéresse estusing et await using, vous devriez pouvoir vous en sortir en ne remplissant que les symboles intégrés. Quelque chose d'aussi simple que ce qui suit devrait fonctionner dans la plupart des cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Symbol.dispose ??= Symbol("Symbol.dispose");
    Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");
    Vous devrez également définir votre compilation targetà es2022ou moins, et configurer votre librairie pour inclure "esnext" ou "esnext.disposable"

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    {
        "compilerOptions": {
            "target": "es2022",
            "lib": ["es2022", "esnext.disposable", "dom"]
        }
    }
    Métadonnées des décorateurs

    TypeScript 5.2 met en œuvre une fonctionnalité ECMAScript à venir appelée métadonnées de décorateur.

    L'idée principale de cette fonctionnalité est de permettre aux décorateurs de créer et de consommer facilement des métadonnées sur n'importe quelle classe sur laquelle ils sont utilisés ou à l'intérieur de celle-ci.

    Chaque fois que des fonctions de décorateur sont utilisées, elles ont désormais accès à une nouvelle propriété de metadata sur leur objet de contexte. La propriété metadata contient un simple objet. Comme JavaScript nous permet d'ajouter des propriétés de manière arbitraire, il peut être utilisé comme un dictionnaire mis à jour par chaque décorateur. Alternativement, puisque chaque objet de metadata sera identique pour chaque partie décorée d'une classe, il peut être utilisé comme une clé dans une map. Une fois que tous les décorateurs sur ou dans une classe ont été exécutés, cet objet peut être accédé sur la classe via Symbol.metadata.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface Context {
        name: string;
        metadata: Record<PropertyKey, unknown>;
    }
     
    function setMetadata(_target: any, context: Context) {
        context.metadata[context.name] = true;
    }
     
    class SomeClass {
        @setMetadata
        foo = 123;
     
        @setMetadata
        accessor bar = "hello!";
     
        @setMetadata
        baz() { }
    }
     
    const ourMetadata = SomeClass[Symbol.metadata];
     
    console.log(JSON.stringify(ourMetadata));
    // { "bar": true, "baz": true, "foo": true }
    Cela peut être utile dans un certain nombre de scénarios différents. Les métadonnées peuvent être attachées à de nombreux usages tels que le débogage, la sérialisation ou l'injection de dépendances avec des décorateurs. Puisque les objets de métadonnées sont créés par classe décorée, les frameworks peuvent soit les utiliser en privé comme clés dans une Mapou une WeakMap, soit leur ajouter des propriétés si nécessaire.

    Par exemple, disons que nous voulions utiliser les décorateurs pour garder une trace des propriétés et des accesseurs sérialisables lorsque nous utilisons JSON.stringifyde la manière suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { serialize, jsonify } from "./serializer";
     
    class Person {
        firstName: string;
        lastName: string;
     
        @serialize
        age: number
     
        @serialize
        get fullName() {
            return `${this.firstName} ${this.lastName}`;
        }
     
        toJSON() {
            return jsonify(this)
        }
     
        constructor(firstName: string, lastName: string, age: number) {
            // ...
        }
    }
    Ici, l'intention est que seuls ageet fullNamesoient sérialisés parce qu'ils sont marqués avec le décorateur @serialize. Nous définissons une méthode toJSONà cette fin, mais elle appelle simplement jsonifyqui utilise les métadonnées créées par @serialize.

    Voici un exemple de définition du module ./serialize.ts :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const serializables = Symbol();
     
    type Context =
        | ClassAccessorDecoratorContext
        | ClassGetterDecoratorContext
        | ClassFieldDecoratorContext
        ;
     
    export function serialize(_target: any, context: Context): void {
        if (context.static || context.private) {
            throw new Error("Can only serialize public instance members.")
        }
        if (typeof context.name === "symbol") {
            throw new Error("Cannot serialize symbol-named properties.");
        }
     
        const propNames =
            (context.metadata[serializables] as string[] | undefined) ??= [];
        propNames.push(context.name);
    }
     
    export function jsonify(instance: object): string {
        const metadata = instance.constructor[Symbol.metadata];
        const propNames = metadata?.[serializables] as string[] | undefined;
        if (!propNames) {
            throw new Error("No members marked with @serialize.");
        }
     
        const pairStrings = propNames.map(key => {
            const strKey = JSON.stringify(key);
            const strValue = JSON.stringify((instance as any)[key]);
            return `${strKey}: ${strValue}`;
        });
     
        return `{ ${pairStrings.join(", ")} }`;
    }
    Ce module possède un local symbolappelé serializablespour stocker et récupérer les noms des propriétés marquées @serializable. Il stocke une liste de ces noms de propriétés dans les métadonnées à chaque invocation de @serializable. Lorsque jsonifyest appelé, la liste des propriétés est extraite des métadonnées et utilisée pour récupérer les valeurs réelles de l'instance, puis pour sérialiser ces noms et ces valeurs.

    L'utilisation d'un symbolrend techniquement ces données accessibles à d'autres personnes. Une alternative pourrait être d'utiliser un WeakMapen utilisant l'objet de métadonnées comme clé. Cela permet de garder les données privées et d'utiliser moins d'assertions de type dans ce cas, mais c'est autrement similaire.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    const serializables = new WeakMap<object, string[]>();
     
    type Context =
        | ClassAccessorDecoratorContext
        | ClassGetterDecoratorContext
        | ClassFieldDecoratorContext
        ;
     
    export function serialize(_target: any, context: Context): void {
        if (context.static || context.private) {
            throw new Error("Can only serialize public instance members.")
        }
        if (typeof context.name !== "string") {
            throw new Error("Can only serialize string properties.");
        }
     
        let propNames = serializables.get(context.metadata);
        if (propNames === undefined) {
            serializables.set(context.metadata, propNames = []);
        }
        propNames.push(context.name);
    }
     
    export function jsonify(instance: object): string {
        const metadata = instance.constructor[Symbol.metadata];
        const propNames = metadata && serializables.get(metadata);
        if (!propNames) {
            throw new Error("No members marked with @serialize.");
        }
        const pairStrings = propNames.map(key => {
            const strKey = JSON.stringify(key);
            const strValue = JSON.stringify((instance as any)[key]);
            return `${strKey}: ${strValue}`;
        });
     
        return `{ ${pairStrings.join(", ")} }`;
    }
    Il est à noter que ces implémentations ne gèrent pas la sous-classe et l'héritage. C'est un exercice qui vous est laissé (et vous pourriez trouver que c'est plus facile dans une version du fichier que dans l'autre !)

    Cette fonctionnalité étant encore récente, la plupart des systèmes d'exécution ne la supporteront pas nativement. Pour l'utiliser, vous aurez besoin d'un polyfill pour Symbol.metadata. Quelque chose d'aussi simple que ce qui suit devrait fonctionner dans la plupart des cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Symbol.metadata ??= Symbol("Symbol.metadata");
    Vous devrez également définir votre compilation targetà es2022ou moins, et configurer votre lib pour inclure "esnext" ou "esnext.decorators".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    {
        "compilerOptions": {
            "target": "es2022",
            "lib": ["es2022", "esnext.decorators", "dom"]
        }
    }
    Éléments de tuple nommés et anonymes

    Les types de tuple prennent en charge des étiquettes ou des noms facultatifs pour chaque élément.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    type Pair<T> = [first: T, second: T];
    Ces étiquettes ne modifient pas ce que vous êtes autorisé à faire avec eux - elles sont uniquement destinées à faciliter la lisibilité et l'outillage.

    Cependant, TypeScript avait auparavant une règle selon laquelle les tuples ne pouvaient pas mélanger des éléments étiquetés et non étiquetés. En d'autres termes, soit aucun élément ne pouvait avoir d'étiquette dans un tuple, soit tous les éléments en avaient besoin.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //  fine - no labels
    type Pair1<T> = [T, T];
     
    //  fine - all fully labeled
    type Pair2<T> = [first: T, second: T];
     
    //  previously an error
    type Pair3<T> = [first: T, T];
    //                         ~
    // Tuple members must all have names
    // or all not have names.
    Cela pourrait être gênant pour les éléments "rest", pour lesquels nous serions obligés d'ajouter un libellé tel que rest ou tail.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    //  previously an error
    type TwoOrMore_A<T> = [first: T, second: T, ...T[]];
    //                                          ~~~~~~
    // Tuple members must all have names
    // or all not have names.
     
    // 
    type TwoOrMore_B<T> = [first: T, second: T, rest: ...T[]];
    Cela signifie également que cette restriction doit être appliquée en interne dans le système de types, ce qui signifie que TypeScript perdrait des étiquettes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    type HasLabels = [a: string, b: string];
    type HasNoLabels = [number, number];
    type Merged = [...HasNoLabels, ...HasLabels];
    //   ^ [number, number, string, string]
    //
    //     'a' and 'b' were lost in 'Merged'
    Dans TypeScript 5.2, la restriction "tout ou rien" sur les étiquettes de tuple a été levée. Le langage peut désormais également préserver les étiquettes lors de la propagation dans un tuple non étiqueté.

    Utilisation plus facile des méthodes pour les unions de tableaux

    Dans les versions précédentes de TypeScript, l'appel d'une méthode sur une union de tableaux pouvait s'avérer pénible.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    declare let array: string[] | number[];
     
    array.filter(x => !!x);
    //    ~~~~~~ error!
    // This expression is not callable.
    //   Each member of the union type '...' has signatures,
    //   but none of those signatures are compatible
    //   with each other.
    Dans cet exemple, TypeScript essaierait de voir si chaque version filterest compatible avec string[] et number[]. Sans stratégie cohérente, TypeScript a jeté ses mains en l'air et a dit "je ne peux pas le faire fonctionner".

    Dans TypeScript 5.2, avant d'abandonner dans ces cas, les unions de tableaux sont traitées comme un cas spécial. Un nouveau type de tableau est construit à partir du type d'élément de chaque membre, puis la méthode est invoquée sur celui-ci.

    Dans l'exemple ci-dessus, string[]| number[] est transformé en (string | number)[] (ou Array<string | number>), et filterest invoqué sur ce type. Il y a un léger inconvénient : filter produira un tableau<chaîne | nombre> au lieu d'une chaîne[] | nombre[] ; mais pour une valeur fraîchement produite, il y a moins de risque que quelque chose "tourne mal".

    Cela signifie que de nombreuses méthodes telles que filter, find, some, everyet reducedevraient toutes pouvoir être invoquées sur des unions de tableaux dans des cas où elles ne l'étaient pas auparavant.

    Chemins d'importation de type uniquement avec les extensions de fichiers d'implémentation TypeScript

    TypeScript permet désormais aux extensions de fichiers de déclaration et d'implémentation d'être incluses dans les chemins d'importation de type uniquement, que l'option allowImportingTsExtensionssoit activée ou non.

    Cela signifie que vous pouvez désormais écrire des instructions import typequi utilisent les extensions de fichiers .ts, .mts, .cts et .tsx.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    import type { JustAType } from "./justTypes.ts";
     
    export function f(param: JustAType) {
        // ...
    }
    Cela signifie également que les types import(), qui peuvent être utilisés à la fois en TypeScript et en JavaScript avec JSDoc, peuvent utiliser ces extensions de fichier.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /**
     * @param {import("./justTypes.ts").JustAType} param
     */
    export function f(param) {
        // ...
    }
    Compléments à la virgule pour les membres d'un objet

    Il peut être facile d'oublier d'ajouter une virgule lors de l'ajout d'une nouvelle propriété à un objet. Auparavant, si vous oubliiez une virgule et que vous demandiez l'auto-complétion, TypeScript donnait des résultats de complétion médiocres et sans rapport avec le sujet.

    TypeScript 5.2 fournit désormais de manière gracieuse des compléments sur les membres d'un objet lorsqu'il manque une virgule. Mais pour éviter de vous infliger une erreur de syntaxe, TypeScript insère automatiquement la virgule manquante.

    Nom : comma-completions-5-2-beta.gif
Affichages : 1664
Taille : 115,1 Ko

    Refactorisation des variables en ligne

    TypeScript 5.2 dispose désormais d'un refactoring permettant d'intégrer le contenu d'une variable dans tous les sites d'utilisation.

    Nom : canvas.png
Affichages : 1655
Taille : 37,4 Ko

    L'utilisation du refactoring "inline variable" éliminera la variable et remplacera toutes les utilisations de la variable par son initialisateur. Notez que cela peut entraîner l'exécution des effets secondaires de l'initialisateur à un moment différent et autant de fois que la variable a été utilisée.

    Vérifications optimisées pour la compatibilité continue des types

    TypeScript étant un système de types structurels, les types doivent parfois être comparés par membre. Cependant, les types récursifs posent quelques problèmes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface A {
        value: A;
        other: string;
    }
     
    interface B {
        value: B;
        other: number;
    }
    En vérifiant si le type Aest compatible avec le type B, TypeScript finira par vérifier si les types de valuede Aet Bsont respectivement compatibles. À ce stade, le système de type doit cesser de vérifier et passer à la vérification d'autres membres. Pour ce faire, le système de types doit savoir quand deux types sont déjà liés.

    Auparavant, TypeScript conservait déjà une pile de paires de types et la parcourait pour déterminer si ces types étaient liés. Lorsque cette pile est peu profonde, ce n'est pas un problème ; mais lorsque la pile n'est pas peu profonde, c'est, euh, un problème.

    Dans TypeScript 5.3, un simple Setpermet de suivre ces informations. Cela a permis de réduire de plus de 33 % le temps passé sur un cas de test signalé qui utilisait la bibliothèque drizzle !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Benchmark 1: old
      Time (mean ± σ):      3.115 s ±  0.067 s    [User: 4.403 s, System: 0.124 s]
      Range (min … max):    3.018 s …  3.196 s    10 runs
     
    Benchmark 2: new
      Time (mean ± σ):      2.072 s ±  0.050 s    [User: 3.355 s, System: 0.135 s]
      Range (min … max):    1.985 s …  2.150 s    10 runs
     
    Summary
      'new' ran
        1.50 ± 0.05 times faster than 'old'
    Changements de rupture et corrections

    TypeScript s'efforce de ne pas introduire inutilement des ruptures ; cependant, nous devons parfois apporter des corrections et des améliorations afin que le code puisse être mieux analysé.

    Changements dans lib.d.tsLes types générés pour le DOM peuvent avoir un impact sur votre base de code. Pour plus d'informations, voir les mises à jour DOM pour TypeScript 5.2.

    labeledElementDeclarationspeut contenir des éléments undefinedAfin de supporter un mélange d'éléments étiquetés et non étiquetés, l'API de TypeScript a légèrement changé. La propriété labeledElementDeclarationsde TupleTypepeut contenir des éléments undefined à chaque position où un élément n'est pas étiqueté.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
      interface TupleType {
    -     labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[];
    +     labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[];
      }
    moduleet moduleResolutiondoivent correspondre aux paramètres récents de Node.js

    Les options --module et --moduleResolution supportent chacune les paramètres node16et nodenext. Il s'agit en fait de paramètres "Node.js modernes" qui devraient être utilisés dans tout projet Node.js récent. Nous avons constaté que lorsque ces deux options ne s'accordent pas sur l'utilisation des paramètres liés à Node.js, les projets sont effectivement mal configurés.

    Dans TypeScript 5.2, lorsque l'on utilise node16ou nodenextpour les options --module et --moduleResolution, TypeScript exige désormais que l'autre option ait un paramètre similaire lié à Node.js. Dans les cas où les paramètres divergent, vous obtiendrez probablement un message d'erreur du type.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Option 'moduleResolution' must be set to 'NodeNext' (or left unspecified) when option 'module' is set to 'NodeNext'.
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Option 'module' must be set to 'Node16' when option 'moduleResolution' is set to 'Node16'.
    Ainsi, par exemple, --module esnext --moduleResolution node16 sera rejeté, mais il est préférable d'utiliser --module nodenextseul, ou --module esnext --moduleResolution bundler.

    Vérification cohérente de l'exportation des symboles fusionnés

    Lorsque deux déclarations fusionnent, elles doivent s'accorder sur le fait qu'elles sont toutes deux exportées. En raison d'un bogue, TypeScript a manqué des cas spécifiques dans des contextes ambiants, comme dans les fichiers de déclaration ou les blocs de declare module. Par exemple, il n'émettrait pas d'erreur dans un cas comme celui-ci, où replaceInFileest déclaré une fois en tant que fonction exportée, et une fois en tant qu'espace de noms non exporté.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    declare module 'replace-in-file' {
        export function replaceInFile(config: unknown): Promise<unknown[]>;
        export {};
     
        namespace replaceInFile {
            export function sync(config: unknown): unknown[];
      }
    }
    Dans un module ambiant, l'ajout d'un export { ... } ou une construction similaire comme export default ... modifie implicitement si toutes les déclarations sont automatiquement exportées. TypeScript reconnaît maintenant cette sémantique malheureusement déroutante de manière plus cohérente, et émet une erreur sur le fait que toutes les déclarations de replaceInFiledoivent être en accord dans leurs modificateurs, et émettra l'erreur suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Individual declarations in merged declaration 'replaceInFile' must be all exported or all local.
    Source : Microsoft

    Et vous ?

    Quel est votre avis sur le sujet ?

    Que pensez-vous des fonctionnalités proposées par cette version de TypeScript ?

    Voir aussi :

    Microsoft annonce la disponibilité de TypeScript 5.2 Beta et présente une liste rapide des nouveautés de TypeScript 5.2

    Microsoft annonce la disponibilité de TypeScript 5.0. Et présente les principaux changements notables depuis la publication de la version bêta

    Microsoft annonce la disponibilité de la version 5.1 de TypeScript, et présente les changements depuis les versions bêta et release candidate
    Contribuez au club : Corrections, suggestions, critiques, ... : Contactez le service news et Rédigez des actualités

Discussions similaires

  1. Microsoft annonce la disponibilité de TypeScript 3.1 RC
    Par Stéphane le calme dans le forum TypeScript
    Réponses: 1
    Dernier message: 28/09/2018, 13h02
  2. Microsoft annonce la disponibilité de TypeScript 2.9 RC
    Par Stéphane le calme dans le forum TypeScript
    Réponses: 5
    Dernier message: 05/06/2018, 10h23
  3. Réponses: 0
    Dernier message: 28/03/2018, 00h20
  4. Microsoft annonce la disponibilité de TypeScript 2.4
    Par Stéphane le calme dans le forum TypeScript
    Réponses: 1
    Dernier message: 28/06/2017, 07h38
  5. Réponses: 0
    Dernier message: 23/02/2017, 12h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo