Existem quatro aspectos diferentes dos enums no TypeScript que você precisa conhecer. Primeiro, algumas definições:
"objeto de pesquisa"
Se você escrever este enum:
enum Foo { X, Y }
O TypeScript emitirá o seguinte objeto:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Vou me referir a isso como o objeto de pesquisa . Seu propósito é duplo: servir como um mapeamento de strings para números , por exemplo, ao escrever Foo.Xou Foo['X'], e servir como um mapeamento de números para strings . Esse mapeamento reverso é útil para fins de depuração ou registro - você geralmente terá o valor 0ou 1e deseja obter a string correspondente "X"ou "Y".
"declarar" ou " ambiente "
No TypeScript, você pode "declarar" coisas que o compilador deve saber, mas não para as quais realmente emitir código. Isso é útil quando você tem bibliotecas como jQuery que definem algum objeto (por exemplo $) sobre o qual deseja digitar informações, mas não precisa de nenhum código criado pelo compilador. A especificação e outras documentações referem-se a declarações feitas dessa maneira como sendo em um contexto "ambiente"; é importante observar que todas as declarações em um .d.tsarquivo são "ambientais" (exigindo um declaremodificador explícito ou tendo-o implicitamente, dependendo do tipo de declaração).
"inlining"
Por motivos de desempenho e tamanho do código, geralmente é preferível ter uma referência a um membro enum substituída por seu equivalente numérico quando compilado:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
A especificação chama isso de substituição , vou chamá-lo inlining porque soa mais legal. Às vezes, você não vai querer que os membros do enum sejam sequenciais, por exemplo, porque o valor do enum pode mudar em uma versão futura da API.
Enums, como funcionam?
Vamos dividir isso por cada aspecto de um enum. Infelizmente, cada uma dessas quatro seções fará referência a termos de todas as outras, então você provavelmente precisará ler tudo isso mais de uma vez.
calculado vs não calculado (constante)
Os membros de Enum podem ser calculados ou não. A especificação chama constantes de membros não computados , mas vou chamá-los de não computados para evitar confusão com const .
Um membro enum calculado é aquele cujo valor não é conhecido em tempo de compilação. As referências a membros computados não podem ser sequenciais, é claro. Por outro lado, um membro enum não calculado é uma vez cujo valor é conhecido em tempo de compilação. As referências a membros não computados são sempre sequenciais.
Quais membros enum são computados e quais não são? Primeiro, todos os membros de um constenum são constantes (ou seja, não calculados), como o nome indica. Para um enum não constante, depende se você está olhando um enum ambiente (declarar) ou um enum não ambiente.
Um membro de a declare enum(isto é, enum ambiente) é constante se e somente se tiver um inicializador. Caso contrário, é calculado. Observe que em a declare enum, apenas inicializadores numéricos são permitidos. Exemplo:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Finalmente, os membros de enums não declarados não constantes são sempre considerados computados. No entanto, suas expressões de inicialização são reduzidas a constantes se forem computáveis em tempo de compilação. Isso significa que os membros enum não constantes nunca são embutidos (esse comportamento mudou no TypeScript 1.5, consulte "Alterações no TypeScript" na parte inferior)
const vs não const
const
Uma declaração enum pode ter o constmodificador. Se um enum for const, todas as referências a seus membros serão incorporadas.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
enums const não produzem um objeto de pesquisa quando compilados. Por esse motivo, é um erro fazer referência Foono código acima, exceto como parte de uma referência de membro. Nenhum Fooobjeto estará presente no tempo de execução.
não const
Se uma declaração enum não tiver o constmodificador, as referências a seus membros serão sequenciadas apenas se o membro não for computado. Um enum não const e não declarado produzirá um objeto lookup.
declarar (ambiente) vs não declarar
Um prefácio importante é que declareno TypeScript tem um significado muito específico: este objeto existe em outro lugar . É para descrever objetos existentes . Usar declarepara definir objetos que não existem realmente pode ter consequências ruins; vamos explorar isso mais tarde.
declarar
A declare enumnão emitirá um objeto de pesquisa. As referências aos seus membros são sequenciais se esses membros forem computados (veja acima em computado vs não computado).
É importante notar que as outras formas de referência a um declare enum são permitidos, por exemplo, este código é não um erro de compilação, mas irá falhar em tempo de execução:
// 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 erro se enquadra na categoria de "Não minta para o compilador". Se você não tiver um objeto nomeado Fooem tempo de execução, não escreva declare enum Foo!
A declare const enumnão é diferente de a const enum, exceto no caso de --preserveConstEnums (veja abaixo).
não declarar
Um enum não declarado produz um objeto de pesquisa se não for const. Inlining é descrito acima.
--preserveConstEnums sinalizador
Este sinalizador tem exatamente um efeito: enums const não declarados emitirá um objeto de pesquisa. Inlining não é afetado. Isso é útil para depuração.
Erros comuns
O erro mais comum é usar um declare enumquando um normal enumou const enumseria mais apropriado. Uma forma comum é 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]);
}
Lembre-se da regra de ouro: nunca declarecoisas que não existam de verdade . Use const enumse quiser sempre inlining ou enumse quiser o objeto de pesquisa.
Mudanças no TypeScript
Entre TypeScript 1.4 e 1.5, houve uma mudança no comportamento (consulte https://github.com/Microsoft/TypeScript/issues/2183 ) para fazer com que todos os membros de enums não declarados não constantes fossem tratados como calculados, mesmo que eles são inicializados explicitamente com um literal. Isso "desfaz o bebê", por assim dizer, tornando o comportamento inlining mais previsível e separando de forma mais clara o conceito de const enumregular enum. Antes dessa mudança, os membros não computados de enums não constantes eram embutidos de forma mais agressiva.