enums - switch - ¿Cómo funcionan las diferentes variantes de enumeración en TypeScript?
typescript enum to string (2)
Hay algunas cosas pasando aquí. Vamos caso por caso.
enumeración
enum Cheese { Brie, Cheddar }
Primero, una simple enumeración antigua. Cuando se compila a JavaScript, emitirá una tabla de búsqueda.
La tabla de búsqueda se ve así:
var Cheese;
(function (Cheese) {
Cheese[Cheese["Brie"] = 0] = "Brie";
Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));
Luego, cuando tiene
Cheese.Brie
en TypeScript, emite
Cheese.Brie
en JavaScript que se evalúa en 0.
Cheese[0]
emite
Cheese[0]
y en realidad se evalúa en
"Brie"
.
const enum
const enum Bread { Rye, Wheat }
¡No se emite ningún código para esto! Sus valores están en línea. Los siguientes emiten el valor 0 en sí mismo en JavaScript:
Bread.Rye
Bread[''Rye'']
const enum
s podría ser útil por razones de rendimiento.
Pero ¿qué pasa con el
Bread[0]
?
Esto generará un error en tiempo de ejecución y su compilador debería detectarlo.
No hay una tabla de búsqueda y el compilador no está en línea aquí.
Tenga en cuenta que en el caso anterior, el indicador --preserveConstEnums hará que Bread emita una tabla de búsqueda. Sin embargo, sus valores aún estarán en línea.
declarar enumeración
Al igual que con otros usos de
declare
,
declare
no emite ningún código y espera que haya definido el código real en otro lugar.
Esto no emite ninguna tabla de búsqueda:
declare enum Wine { Red, Wine }
Wine.Red
emite
Wine.Red
en JavaScript, pero no habrá ninguna tabla de búsqueda de Wine para hacer referencia, por lo que es un error a menos que lo haya definido en otro lugar.
declarar const enum
Esto no emite ninguna tabla de búsqueda:
declare const enum Fruit { Apple, Pear }
Pero lo hace en línea!
Fruit.Apple
emite 0. Pero, de nuevo,
Fruit[0]
generará un error en tiempo de ejecución porque no está en línea y no hay una tabla de búsqueda.
He escrito esto en this patio de recreo. Recomiendo jugar allí para comprender qué TypeScript emite qué JavaScript.
TypeScript tiene muchas formas diferentes de definir una enumeración:
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 trato de usar un valor de
Gamma
en tiempo de ejecución, aparece un error porque
Gamma
no está definido, pero ese no es el caso de
Delta
o
Alpha
.
¿Qué significa
const
o
declare
en las declaraciones aquí?
También hay un indicador del compilador
preserveConstEnums
: ¿cómo interactúa esto con estos?
Hay cuatro aspectos diferentes para las enumeraciones en TypeScript que debe tener en cuenta. Primero, algunas definiciones:
"objeto de búsqueda"
Si escribes esta enumeración:
enum Foo { X, Y }
TypeScript emitirá el siguiente objeto:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Me referiré a esto como
el objeto de búsqueda
.
Su propósito es doble: servir como un mapeo de
cadenas
a
números
, por ejemplo, al escribir
Foo.X
o
Foo[''X'']
, y servir como un mapeo de
números
a
cadenas
.
Esa asignación inversa es útil para fines de depuración o registro: a menudo tendrá el valor
0
o
1
y desea obtener la cadena correspondiente
"X"
o
"Y"
.
"declarar" o " ambiente "
En TypeScript, puede "declarar" cosas que el compilador debe saber, pero en realidad no emitir código.
Esto es útil cuando tiene bibliotecas como jQuery que definen algún objeto (por ejemplo,
$
) sobre el que desea escribir información, pero no necesita ningún código creado por el compilador.
La especificación y otra documentación se refieren a declaraciones hechas de esta manera como en un contexto "ambiental";
Es importante tener en cuenta que todas las declaraciones en un archivo
.d.ts
son "ambientales" (ya sea que requieran un modificador de
declare
explícito o que lo tengan implícitamente, dependiendo del tipo de declaración).
"en línea"
Por razones de rendimiento y tamaño de código, a menudo es preferible reemplazar una referencia a un miembro enum por su equivalente numérico cuando se compila:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
La especificación llama a esta sustitución , lo llamaré en línea porque suena más fresco. A veces no querrá que los miembros de enumeración estén en línea, por ejemplo, porque el valor de enumeración puede cambiar en una versión futura de la API.
Enums, ¿cómo funcionan?
Analicemos esto por cada aspecto de una enumeración. Desafortunadamente, cada una de estas cuatro secciones hará referencia a los términos de todas las demás, por lo que probablemente deba leer todo esto más de una vez.
calculada vs no calculada (constante)
Los miembros de Enum se pueden calcular o no. La especificación llama a miembros no computados constantes , pero los llamaré no computados para evitar confusión con const .
Un miembro de enumeración calculado es aquel cuyo valor no se conoce en tiempo de compilación. Las referencias a miembros calculados no se pueden incluir, por supuesto. Por el contrario, un miembro de enumeración no calculado es una vez cuyo valor se conoce en tiempo de compilación. Las referencias a miembros no computados siempre están en línea.
¿Qué miembros de enumeración se calculan y cuáles no?
Primero, todos los miembros de un
const
enum son constantes (es decir, no computados), como su nombre lo indica.
Para una enumeración no constante, depende de si está mirando una enumeración
ambiental
(declarar) o una enumeración no ambiental.
Un miembro de una
declare enum
(es decir, enumeración ambiental) es constante
si y solo si
tiene un inicializador.
De lo contrario, se calcula.
Tenga en cuenta que en una
declare enum
, solo se permiten inicializadores numéricos.
Ejemplo:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Finalmente, los miembros de enumeraciones sin declaración no constantes siempre se consideran calculados. Sin embargo, sus expresiones de inicialización se reducen a constantes si son computables en tiempo de compilación. Esto significa que los miembros de enumeración no constantes nunca están en línea (este comportamiento cambió en TypeScript 1.5, consulte "Cambios en TypeScript" en la parte inferior)
const vs no const
const
Una declaración enum puede tener el modificador
const
.
Si una enumeración es
const
,
todas las
referencias a sus miembros están en línea.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
las enumeraciones constantes no producen un objeto de búsqueda cuando se compilan.
Por esta razón, es un error hacer referencia a
Foo
en el código anterior, excepto como parte de una referencia de miembro.
Ningún objeto
Foo
estará presente en tiempo de ejecución.
no constante
Si una declaración enum no tiene el modificador
const
, las referencias a sus miembros se insertan solo si el miembro no se computa.
Una enumeración que no sea constante ni declarada producirá un objeto de búsqueda.
declarar (ambiente) vs no declarar
Un prefacio importante es que
declare
en TypeScript tiene un significado muy específico:
este objeto existe en otro lugar
.
Es para describir objetos
existentes
.
Usar
declare
para definir objetos que en realidad no existen pueden tener malas consecuencias;
los exploraremos más tarde.
declarar
Una
declare enum
no emitirá un objeto de búsqueda.
Las referencias a sus miembros están en línea si esos miembros se computan (ver arriba en computados versus no computados).
Es importante tener en cuenta que
se
permiten otras formas de referencia para
declare enum
una
declare enum
, por ejemplo, este código
no
es un error de compilación pero fallará en tiempo de ejecución:
// 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
Este error pertenece a la categoría de "No le mientas al compilador".
Si no tiene un objeto llamado
Foo
en tiempo de ejecución, ¡no escriba
declare enum Foo
!
Una
declare const enum
no es diferente de una
const enum
, excepto en el caso de --preserveConstEnums (ver más abajo).
no declarar
Una enumeración no declarada produce un objeto de búsqueda si no es
const
.
La alineación se describe arriba.
--preserveConstEnums flag
Este indicador tiene exactamente un efecto: las enumeraciones constantes no declaradas emitirán un objeto de búsqueda. La inclinación no se ve afectada. Esto es útil para la depuración.
Errores comunes
El error más común es usar una
declare enum
cuando una
enum
regular o una
enum
const enum
sería más apropiada.
Una forma común es esta:
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]);
}
Recuerde la regla de oro:
nunca
declare
cosas que en realidad no existen
.
Utilice
const enum
si siempre desea alinear, o
enum
si desea el objeto de búsqueda.
Cambios en TypeScript
Entre TypeScript 1.4 y 1.5, hubo un cambio en el comportamiento (consulte
https://github.com/Microsoft/TypeScript/issues/2183
) para hacer que todos los miembros de enumeraciones no declaradas y no constantes sean tratadas como calculadas, incluso si se inicializan explícitamente con un literal.
Esto "separa al bebé", por así decirlo, lo que hace que el comportamiento en línea sea más predecible y separe más limpiamente el concepto de
const enum
del
enum
regular.
Antes de este cambio, los miembros no computados de enumeraciones no constantes se alineaban de manera más agresiva.