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.X
ou 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 0
ou 1
e 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.ts
arquivo são "ambientais" (exigindo um declare
modificador 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 const
enum 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 const
modificador. 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 Foo
no código acima, exceto como parte de uma referência de membro. Nenhum Foo
objeto estará presente no tempo de execução.
não const
Se uma declaração enum não tiver o const
modificador, 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 declare
no TypeScript tem um significado muito específico: este objeto existe em outro lugar . É para descrever objetos existentes . Usar declare
para definir objetos que não existem realmente pode ter consequências ruins; vamos explorar isso mais tarde.
declarar
A declare enum
nã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 Foo
em tempo de execução, não escreva declare enum Foo
!
A declare const enum
nã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 enum
quando um normal enum
ou const enum
seria 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 declare
coisas que não existam de verdade . Use const enum
se quiser sempre inlining ou enum
se 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 enum
regular enum
. Antes dessa mudança, os membros não computados de enums não constantes eram embutidos de forma mais agressiva.