web-dev-qa-db-fra.com

Comment fonctionnent les différentes variantes d'énumération dans TypeScript?

TypeScript a un tas de différentes façons de définir une énumération:

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 au moment de 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? Que signifient const ou declare dans les déclarations ici?

Il y a aussi un drapeau de compilateur preserveConstEnums - comment cela interagit-il avec ceux-ci?

83
Ryan Cavanaugh

Il faut que vous connaissiez quatre types d’énumérations dans TypeScript. Tout d'abord, quelques définitions:

"objet de recherche"

Si vous écrivez cette énumération:

enum Foo { X, Y }

TypeScript émettra l'objet suivant:

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

Je ferai référence à cela comme l'objet de recherche . Son objectif est double: servir de mappage de chaînes à nombres , par exemple en écrivant Foo.X ou Foo['X'], et pour servir de mappage de nombres à chaînes . Ce mappage inversé est utile pour le débogage ou la journalisation - vous aurez souvent la valeur 0 ou 1 et veulent obtenir la chaîne correspondante "X" ou "Y".

"déclarer" ou " ambient "

Dans TypeScript, vous pouvez "déclarer" des choses que le compilateur devrait connaître, mais pas réellement émettre de code. Ceci est utile lorsque vous avez des bibliothèques comme jQuery qui définissent un objet (par exemple, $) sur lequel vous souhaitez taper des informations, mais n'avez pas besoin de code créé par le compilateur. Les spécifications et autres documents font référence aux déclarations faites de cette manière dans un contexte "ambiant"; il est important de noter que toutes les déclarations dans un .d.ts fichier sont "ambiants" (nécessitant soit un modificateur explicite declare, soit implicitement, en fonction du type de déclaration).

"inline"

Pour des raisons de performances et de taille du code, il est souvent préférable de remplacer une référence par un membre enum 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 glacière. Parfois, vous ne voulez pas que les membres enum soient en ligne, par exemple parce que la valeur enum pourrait changer dans une future version de l'API.


Enums, comment fonctionnent-ils?

Décomposons cela par chaque aspect d'une enum. Malheureusement, chacune de ces quatre sections va faire référence à des termes de toutes les autres, vous aurez donc probablement besoin de lire tout cela à plusieurs reprises.

calculé vs non calculé (constant)

Les membres enum peuvent être calculés ou non. La spéc 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 au moment de la compilation. Les références aux membres calculés ne peuvent pas être en ligne, bien sûr. Inversement, un membre enum non calculé est une fois dont la valeur est connue au moment de 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 le sont pas? Tout d’abord, tous les membres d’une énumération const sont constants (c’est-à-dire non calculés), comme le 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 (ambiant) 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 déclarantes et non constantes sont toujours considérés comme étant calculés. Cependant, leurs expressions d'initialisation sont réduites aux constantes si elles sont calculables au moment de la compilation. Cela signifie que les membres non const enum ne sont jamais en ligne (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 en ligne.

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

les énumérations constantes ne produisent pas d'objet de recherche lors de la compilation. Pour cette raison, il est erroné de référencer Foo dans le code ci-dessus, sauf dans le cadre d'une référence de membre. Aucun objet 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 en ligne que si le membre n'est pas calculé. Une énumération non-const, non-declare produira un objet 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 objet existe ailleurs . C'est pour décrire des objets existants . L'utilisation de declare pour définir des objets qui n'existent pas réellement peut avoir de mauvaises conséquences. nous allons explorer ceux plus tard.

déclarer

UNE declare enum n'émettra pas d'objet de recherche. Les références à ses membres sont en ligne si ces membres sont calculés (voir ci-dessus sur calculés vs non calculés).

Il est important de noter que d'autres formes de référence à un declare enum sont autorisés, par exemple. ce code est pas une erreur de compilation mais échouera au moment de 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 mentez pas au compilateur". Si vous n'avez pas d'objet nommé Foo au moment de l'exécution, n'écrivez pas declare enum Foo!

UNE declare const enum n'est pas différent d'un const enum, sauf dans le cas de --preserveConstEnums (voir ci-dessous).

non-déclarer

Une énumération de non-déclaration produit un objet de recherche s'il ne s'agit pas de const. L'inlignage est décrit ci-dessus.

--preserveConstEnums flag

Cet indicateur a exactement un effet: les énums constants non déclarés émettront un objet de recherche. L'inlignage n'est pas affecté. Ceci est utile pour le débogage.


Erreurs courantes

L'erreur la plus courante est d'utiliser un declare enum quand un enum ou const enum 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: Jamais declare de choses qui n'existent pas réellement . Utilisation const enum si vous voulez toujours en ligne, ou enum si vous voulez l’objet de recherche.


Changements dans TypeScript

Entre TypeScript 1.4 et 1.5, le comportement a changé (voir https://github.com/Microsoft/TypeScript/issues/218 ) pour rendre tous les membres d'énums non constants et non déclarés. être traités comme des calculs, même s'ils sont explicitement initialisés avec un littéral. Ce "bébé non divisé", pour ainsi dire, rend le comportement en ligne plus prévisible et sépare plus proprement le concept de const enum de normal enum. Avant ce changement, les membres non calculés des éléments non const enum étaient intégrés plus agressivement.

195
Ryan Cavanaugh

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

enum

enum Cheese { Brie, Cheddar }

Tout d'abord, une vieille plaine ancienne. Une fois compilé en JavaScript, ceci émettra une table de recherche.

La table de recherche ressemble à ceci:

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

Puis quand vous avez Cheese.Brie en TypeScript, il émet Cheese.Brie en JavaScript évaluée à 0. Cheese[0] émet Cheese[0] et évalue réellement à "Brie".

const enum

const enum Bread { Rye, Wheat }

Aucun code n'est réellement émis pour cela! Ses valeurs sont en ligne. Les éléments suivants émettent la valeur 0 elle-même en JavaScript:

Bread.Rye
Bread['Rye']

const enums 'inlining peut être utile pour des raisons de performances.

Mais qu'en est-il de Bread[0]? Cela entraînera une erreur lors de l'exécution et votre compilateur devrait l'attraper. Il n'y a pas de table de correspondance et le compilateur n'est pas en ligne ici.

Notez que dans le cas ci-dessus, l'indicateur --preserveConstEnums fera que Bread émettra une table de recherche. Ses valeurs seront toujours en ligne si.

déclarer enum

Comme avec 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 recherche:

declare enum Wine { Red, Wine }

Wine.Red émet Wine.Red en JavaScript, mais il n’y aura pas de table de recherche Wine à référencer, donc c’est une erreur, à moins que vous ne l’ayez définie ailleurs.

déclarer const enum

Cela n'émet aucune table de recherche:

declare const enum Fruit { Apple, Pear }

Mais c'est inline! Fruit.Apple émet 0. Mais encore une fois Fruit[0] aura une erreur lors de l'exécution car ce n'est pas en ligne et il n'y a pas de table de correspondance.

J'ai écrit ceci dans this terrain de jeu. Je recommande de jouer là-bas pour comprendre quel TypeScript émet quel JavaScript.

17
Kat