Comment les différentes variantes d’énumération fonctionnent-elles dans TypeScript?

TypeScript a plusieurs façons de définir un enum:

enum Alpha { X, Y, Z } const enum Beta { X, Y, Z } declare enum Gamma { X, Y, Z } declare const enum Delta { X, Y, Z } 

Si j’essaie d’utiliser une valeur de Gamma à l’exécution, j’obtiens une erreur car Gamma n’est pas défini, mais ce n’est pas le cas pour Delta ou Alpha ? Qu’est-ce que const ou declare méchant sur les déclarations ici?

Il y a aussi un indicateur du compilateur preserveConstEnums – comment cela interagit-il avec ceux-ci?

    Vous devez connaître quatre aspects des énumérations dans TypeScript. Premièrement, quelques définitions:

    “object de recherche”

    Si vous écrivez cette énumération:

     enum Foo { X, Y } 

    TypeScript émettra l’object suivant:

     var Foo; (function (Foo) { Foo[Foo["X"] = 0] = "X"; Foo[Foo["Y"] = 1] = "Y"; })(Foo || (Foo = {})); 

    Je ferai référence à ceci comme object de recherche . Son but est double: servir de mappage entre des chaînes et des nombres , par exemple lors de l’écriture de Foo.X ou de Foo['X'] , et servir de mappage entre des nombres et des chaînes . Ce reverse mapping est utile à des fins de débogage ou de journalisation – vous aurez souvent la valeur 0 ou 1 et souhaitez obtenir la chaîne correspondante "X" ou "Y" .

    “déclarer” ou ” ambiant

    Dans TypeScript, vous pouvez “déclarer” les éléments que le compilateur doit connaître, mais ne les émet pas réellement. Ceci est utile lorsque vous avez des bibliothèques comme jQuery qui définissent un object (par exemple $ ) sur lequel vous voulez des informations de type, mais qui ne nécessitent aucun code créé par le compilateur. La spécification et d’autres documents font référence à des déclarations faites de cette manière dans un contexte “ambiant”; il est important de noter que toutes les déclarations dans un fichier .d.ts sont “ambient” (nécessitant un modificateur de declare explicite ou implicitement, selon le type de déclaration).

    “inlining”

    Pour des raisons de performances et de taille de code, il est souvent préférable de faire référence à un membre enum remplacé par son équivalent numérique lors de la compilation:

     enum Foo { X = 4 } var y = Foo.X; // emits "var y = 4"; 

    La spécification appelle cette substitution , je l’appellerai en ligne parce que ça sonne plus froid. Parfois, vous ne voulez pas que les membres d’énumération soient intégrés, par exemple parce que la valeur enum peut changer dans une future version de l’API.


    Enums, comment fonctionnent-ils?

    Décomposons ceci par chaque aspect d’un enum. Malheureusement, chacune de ces quatre sections va faire référence à des termes de tous les autres, donc vous aurez probablement besoin de lire tout cela plus d’une fois.

    calculé vs non calculé (constant)

    Les membres Enum peuvent être calculés ou non. La spécification appelle des membres non calculés constants , mais je les appellerai non calculés pour éviter toute confusion avec const .

    Un membre enum calculé est un membre dont la valeur n’est pas connue à la compilation. Les références aux membres calculés ne peuvent évidemment pas être insérées. Inversement, un membre enum non calculé est une fois dont la valeur est connue à la compilation. Les références aux membres non calculés sont toujours en ligne.

    Quels sont les membres énumérés et ceux qui ne sont pas calculés? Tout d’abord, tous les membres d’un const enum sont constants (c’est-à-dire non calculés), comme leur nom l’indique. Pour un enum non-const, cela dépend si vous regardez un enum ambient (declare) ou un enum non-ambient.

    Un membre d’un declare enum (c’est-à-dire enum ambient) est constant si et seulement si il possède un initialiseur. Sinon, il est calculé. Notez que dans un declare enum , seuls les initialiseurs numériques sont autorisés. Exemple:

     declare enum Foo { X, // Computed Y = 2, // Non-computed Z, // Computed! Not 3! Careful! Q = 1 + 1 // Error } 

    Enfin, les membres des énumérations non-const non déclarées sont toujours considérés comme calculés. Cependant, leurs expressions d’initialisation sont réduites à des constantes si elles sont calculables au moment de la compilation. Cela signifie que les membres non-const enum ne sont jamais intégrés (ce comportement a changé dans TypeScript 1.5, voir “Modifications dans TypeScript” en bas)

    const vs non-const

    const

    Une déclaration enum peut avoir le modificateur const . Si une énumération est const , toutes les références à ses membres sont insérées.

     const enum Foo { A = 4 } var x = Foo.A; // emitted as "var x = 4;", always 

    Les const const ne produisent pas d’object de recherche lorsqu’ils sont compilés. Pour cette raison, il est erroné de faire référence à Foo dans le code ci-dessus, sauf dans le cadre d’une référence de membre. Aucun object Foo ne sera présent à l’exécution.

    non-const

    Si une déclaration enum n’a pas le modificateur const , les références à ses membres ne sont insérées que si le membre n’est pas calculé. Un enum non-const, non-declare produira un object de recherche.

    déclarer (ambiant) vs non-déclarer

    Une préface importante est que declare dans TypeScript a une signification très spécifique: cet object existe ailleurs . C’est pour décrire des objects existants . L’utilisation de la declare pour définir des objects qui n’existent pas réellement peut avoir de graves conséquences. nous les explorerons plus tard.

    déclarer

    Une declare enum n’émettra pas d’object de recherche. Les références à ses membres sont insérées si ces membres sont calculés (voir ci-dessus sur calculés et non calculés).

    Il est important de noter que d’autres formes de référence à un declare enum sont autorisées, par exemple, ce code n’est pas une erreur de compilation mais échouera à l’exécution:

     // Note: Assume no other file has actually created a Foo var at runtime declare enum Foo { Bar } var s = 'Bar'; var b = Foo[s]; // Fails 

    Cette erreur relève de la catégorie “Ne pas mentir au compilateur”. Si vous n’avez pas d’object nommé Foo à l’exécution, n’écrivez pas declare enum Foo !

    Une declare const enum n’est pas différente d’un const enum , sauf dans le cas de –preserveConstEnums (voir ci-dessous).

    non-déclarant

    Une énumération non déclarée produit un object de recherche s’il n’est pas const . La doublure est décrite ci-dessus.

    –preserveConstEnums flag

    Cet indicateur a exactement un effet: les const const des non-declare émettront un object de recherche. Inlining n’est pas affecté. Ceci est utile pour le débogage.


    Erreurs courantes

    L’erreur la plus courante est d’utiliser un declare enum lorsqu’un enum ou un enum régulier serait plus approprié. Une forme commune est la suivante:

     module MyModule { // Claiming this enum exists with 'declare', but it doesn't... export declare enum Lies { Foo = 0, Bar = 1 } var x = Lies.Foo; // Depend on inlining } module SomeOtherCode { // x ends up as 'undefined' at runtime import x = MyModule.Lies; // Try to use lookup object, which ought to exist // runtime error, canot read property 0 of undefined console.log(x[x.Foo]); } 

    Rappelez-vous la règle d’or: Ne declare jamais des choses qui n’existent pas réellement . Utilisez const enum si vous voulez toujours insérer ou enum si vous voulez l’object de recherche.


    Changements dans TypeScript

    Entre TypeScript 1.4 et 1.5, le comportement a été modifié (voir https://github.com/Microsoft/TypeScript/issues/2183 ) pour que tous les membres des non-const non-déclar soient traités comme calculés, même si ils sont explicitement initialisés avec un littéral. Cela “déchaîne le bébé”, pour ainsi dire, rendant le comportement inline plus prévisible et séparant plus proprement le concept de const enum du enum régulier. Avant ce changement, les membres non calculés des énoncés non-const étaient plus agressifs.

    Il y a quelques petites choses qui se passent ici. Allons au cas par cas.

    enum

     enum Cheese { Brie, Cheddar } 

    Tout d’abord, une simple vieille enum. Lorsqu’il est compilé en JavaScript, cela émet une table de recherche.

    La table de consultation ressemble à ceci:

     var Cheese; (function (Cheese) { Cheese[Cheese["Brie"] = 0] = "Brie"; Cheese[Cheese["Cheddar"] = 1] = "Cheddar"; })(Cheese || (Cheese = {})); 

    Ensuite, quand vous avez Cheese.Brie dans TypeScript, il émet Cheese.Brie en JavaScript qui a la valeur 0. Cheese[0] émet Cheese[0] et est évalué à "Brie" .

    const enum

     const enum Bread { Rye, Wheat } 

    Aucun code n’est émis pour cela! Ses valeurs sont en ligne. Les éléments suivants émettent la valeur 0 dans JavaScript:

     Bread.Rye Bread['Rye'] 

    const enum s ‘inline peut être utile pour des raisons de performance.

    Mais qu’en est-il du Bread[0] ? Ce sera une erreur lors de l’exécution et votre compilateur devrait l’attraper. Il n’y a pas de table de consultation et le compilateur n’est pas en ligne ici.

    Notez que dans le cas ci-dessus, l’indicateur –preserveConstEnums provoquera l’émission d’une table de consultation par Bread. Ses valeurs seront toujours en ligne cependant.

    déclarer enum

    Comme pour les autres utilisations de declare , declare n’émet aucun code et s’attend à ce que vous ayez défini le code réel ailleurs. Cela n’émet aucune table de consultation:

     declare enum Wine { Red, Wine } 

    Wine.Red émet Wine.Red en JavaScript, mais il n’y aura pas de table de consultation de Wine à référencer, c’est donc une erreur sauf si vous l’avez définie ailleurs.

    déclarer const enum

    Cela n’émet aucune table de consultation:

     declare const enum Fruit { Apple, Pear } 

    Mais c’est vrai! Fruit.Apple émet 0. Encore une fois, Fruit[0] sera en erreur à l’exécution parce qu’il n’est pas en ligne et qu’il n’y a pas de table de consultation.

    Je l’ai écrit dans cette aire de jeux. Je recommande d’y jouer pour comprendre quel typeScript émet quel JavaScript.