Il y a quatre aspects différents des énumérations dans TypeScript dont vous devez être conscient. 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 = {}));
J'appellerai cela l'objet de recherche . Son objectif est double: servir de correspondance entre les chaînes et les nombres , par exemple lors de l'écriture Foo.X
ou Foo['X']
, et servir de correspondance entre les nombres et les chaînes . Ce mappage inversé 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» des choses que le compilateur devrait connaître, mais pas réellement pour lesquelles émettre du code. Ceci est utile lorsque vous avez des bibliothèques comme jQuery qui définissent un objet (par exemple $
) sur lequel vous voulez des informations de type, mais qui n'ont pas besoin de code créé par le compilateur. La spécification et d'autres documents se réfèrent aux déclarations faites de cette façon comme étant dans un contexte «ambiant»; il est important de noter que toutes les déclarations dans un .d.ts
fichier sont "ambiantes" (soit nécessitant un declare
modificateur explicite, soit l'avoir 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 remplacer une référence à 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 inlining car elle sonne plus cool. Parfois, vous ne souhaiterez pas que les membres d'énumération soient insérés, par exemple parce que la valeur d'énumération pourrait changer dans une version future de l'API.
Enums, comment fonctionnent-ils?
Décomposons cela par chaque aspect d'une énumération. Malheureusement, chacune de ces quatre sections fera référence aux termes de toutes les autres, vous aurez donc probablement besoin de lire tout cela plus d'une fois.
calculée vs non calculée (constante)
Les membres d'énumération peuvent être calculés ou non. La spécification appelle les 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 insérées, 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 insérées.
Quels membres d'énumération sont calculés et lesquels ne sont pas calculés? Premièrement, tous les membres d'une const
énumération sont constants (c'est-à-dire non calculés), comme son nom l'indique. Pour une énumération non-const, cela dépend si vous regardez une énumération ambiante (déclarée) ou une énumération non ambiante.
Un membre de a declare enum
(ie ambient enum) est constant si et seulement s'il a un initialiseur. Sinon, il est calculé. Notez que dans a 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éclarées non const 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 d'énumération non const ne sont jamais incorporé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 const
modificateur. Si une énumération est const
, toutes les références à ses membres sont incorporées.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
Les énumérations const ne produisent pas d'objet de recherche lors de la compilation. 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 Foo
objet ne sera présent lors de l'exécution.
non const
Si une déclaration d'énumération n'a pas le const
modificateur, les références à ses membres sont insérées uniquement si le membre n'est pas calculé. Une énumération non const et non déclarée 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 . Utiliser declare
pour définir des objets qui n'existent pas réellement peut avoir de mauvaises conséquences; nous les explorerons plus tard.
déclarer
A declare enum
n'émettra pas d'objet de recherche. Les références à ses membres sont insérées si ces membres sont calculés (voir ci-dessus sur calculé vs non calculé).
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 va échouer à 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
!
A declare const enum
n'est pas différent de a const enum
, sauf dans le cas de --preserveConstEnums (voir ci-dessous).
non-déclarer
Une énumération non déclarée produit un objet de recherche si ce n'est pas le cas const
. L'intégration est décrite ci-dessus.
--preserveConstEnums indicateur
Cet indicateur a exactement un effet: les énumérations const non déclarées émettront un objet de recherche. L'intégration n'est pas affectée. Ceci est utile pour le débogage.
Erreurs courantes
L'erreur la plus courante est d'utiliser un declare enum
quand un régulier enum
ou const enum
serait plus approprié. Une forme courante 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 des declare
choses qui n'existent pas . À utiliser const enum
si vous voulez toujours l'incrustation ou enum
si vous voulez l'objet de recherche.
Changements dans TypeScript
Entre TypeScript 1.4 et 1.5, il y a eu un changement dans le comportement (voir https://github.com/Microsoft/TypeScript/issues/2183 ) pour que tous les membres d'énumérations non déclarées non const soient traités comme calculés, même si ils sont explicitement initialisés avec un littéral. Ce «débris le bébé», pour ainsi dire, rend le comportement en ligne plus prévisible et sépare plus proprement le concept de const enum
régulier enum
. Avant ce changement, les membres non calculés des non-const enums étaient intégrés de manière plus agressive.