11# TypeScript String Enums
22
3- Typesafe string enums in TypeScript.
3+ Typesafe string enums in TypeScript pre-2.4 .
44
5- [ ![ Build Status] ( https://travis-ci.org/dphilipson/typescript-string-enums.svg?branch=master )] ( https://travis-ci.org/dphilipson/typescript-string-enums )
5+ [ ![ Build
6+ Status] ( https://travis-ci.org/dphilipson/typescript-string-enums.svg?branch=master )] ( https://travis-ci.org/dphilipson/typescript-string-enums )
7+
8+ ** As of TypeScript 2.4, this library is made mostly obsolete by native string
9+ enums** (see the
10+ [ announcement] ( https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/ ) ).
11+ I recommend that most users who are on at least TypeScript 2.4 now use native
12+ enums instead. There are still a few minor reasons to continue to use this
13+ library, as discussed in the [ Advantages over native string
14+ enums] ( #advantages-over-native-string-enums ) section.
615
716## Table of Contents
817
@@ -18,6 +27,7 @@ Typesafe string enums in TypeScript.
1827 - [ Why not built-in enums?] ( #why-not-built-in-enums )
1928 - [ Why not string literals?] ( #why-not-string-literals )
2029* [ How It Works] ( #how-it-works )
30+ * [ Advantages over native string enums] ( #advantages-over-native-string-enums )
2131* [ Acknowledgements] ( #acknowledgements )
2232
2333## Installation
@@ -26,8 +36,8 @@ Typesafe string enums in TypeScript.
2636npm install --save typescript-string-enums
2737```
2838
29- This library requires TypeScript 2.2 or later. If you require TypeScript 2.1 compatibility, version
30- 0.2.0 of this library is the last one with support.
39+ This library requires TypeScript 2.2 or later. If you require TypeScript 2.1
40+ compatibility, version 0.2.0 of this library is the last one with support.
3141
3242## Usage
3343
@@ -80,9 +90,10 @@ function saySomethingAboutState(state: State) {
8090}
8191```
8292
83- Instead of a list of values, an object may be passed instead if it is desired that the string values
84- be different from the constant names. This also has the advantage of allowing JSDoc comments to be
85- specified on individual values. For example:
93+ Instead of a list of values, an object may be passed instead if it is desired
94+ that the string values be different from the constant names. This also has the
95+ advantage of allowing JSDoc comments to be specified on individual values. For
96+ example:
8697
8798``` javascript
8899export const Status = Enum ({
@@ -101,7 +112,8 @@ console.log(Status.RUNNING); // -> "running"
101112
102113#### ` Enum.isType(enum, value) `
103114
104- ` Enum.isType ` checks if a value is of a given enum type and can be used as a type guard. For example:
115+ ` Enum.isType ` checks if a value is of a given enum type and can be used as a
116+ type guard. For example:
105117
106118``` javascript
107119const Color = Enum (" BLACK" , " WHITE" );
@@ -150,9 +162,10 @@ const values = Enum.values(FileType);
150162
151163#### Enum.ofKeys(object)
152164
153- Creates a new enum with the same keys as the provided enum or object and whose values are equal to
154- its keys. This is most useful if for some reason it is necessary to do string comparisons against
155- the keys of an enum rather than the values. For example:
165+ Creates a new enum with the same keys as the provided enum or object and whose
166+ values are equal to its keys. This is most useful if for some reason it is
167+ necessary to do string comparisons against the keys of an enum rather than the
168+ values. For example:
156169
157170``` javascript
158171const ErrorColor = Enum ({ OK : " green" , ERROR : " red" });
@@ -169,15 +182,17 @@ if (errorLevel === ErrorLevel.ERROR) {
169182
170183## Motivation
171184
172- Enums are useful for cleanly specifying a type that can take one of a few specific values.
173- TypeScript users typically implement enums in one of two ways: built-in
174- [ TypeScript enums] ( https://www.typescriptlang.org/docs/handbook/enums.html ) or string literals, but
175- each of these has drawbacks.
185+ Enums are useful for cleanly specifying a type that can take one of a few
186+ specific values. TypeScript users typically implement enums in one of two ways:
187+ built-in [ TypeScript
188+ enums] ( https://www.typescriptlang.org/docs/handbook/enums.html ) or string
189+ literals, but each of these has drawbacks.
176190
177191### Why not built-in enums?
178192
179- Built-in enums have one big drawback. Their runtime value is a number, which is annoying during
180- development and makes them unsuitable for use with external APIs.
193+ Built-in enums have one big drawback. Their runtime value is a number, which is
194+ annoying during development and makes them unsuitable for use with external
195+ APIs.
181196
182197``` javascript
183198enum Status {
@@ -199,18 +214,20 @@ type Status = "RUNNING" | "STOPPED";
199214type TriathlonStage = " SWIMMING" | " CYCLING" | " RUNNING" ;
200215```
201216
202- Then if at a later stage I want to change ` Status ` to be ` "STARTED" | "STOPPED" ` , there's no easy
203- way to do it. I can't globally find/replace ` "RUNNING" ` to ` "STARTED" ` because it will also change
204- the unrelated string constants representing ` TriathlonStage ` . Instead, I have to examine every
205- occurrance of the string ` "RUNNING" ` to see if it needs to change. Besides, these kinds of global
206- non-semantic substitutions should make you nervous.
217+ Then if at a later stage I want to change ` Status ` to be `"STARTED" |
218+ "STOPPED"`, there's no easy way to do it. I can't globally find/replace
219+ ` "RUNNING" ` to ` "STARTED" ` because it will also change the unrelated string
220+ constants representing ` TriathlonStage ` . Instead, I have to examine every
221+ occurrance of the string ` "RUNNING" ` to see if it needs to change. Besides,
222+ these kinds of global non-semantic substitutions should make you nervous.
207223
208- Another disadvantage of string literals comes when using IDE autocomplete features. It's convenient
209- to be able to type ` Status. ` and have autocomplete suggest ` Status.RUNNING ` and ` Status.STOPPED ` ,
210- but with string literals no such suggestion is possible.
224+ Another disadvantage of string literals comes when using IDE autocomplete
225+ features. It's convenient to be able to type ` Status. ` and have autocomplete
226+ suggest ` Status.RUNNING ` and ` Status.STOPPED ` , but with string literals no such
227+ suggestion is possible.
211228
212- I might try to solve both problems by introducing constants for the string literals, but this has
213- issues as well:
229+ I might try to solve both problems by introducing constants for the string
230+ literals, but this has issues as well:
214231
215232``` javascript
216233// Typo on "STOPPED" not caught by anything below without additional boilerplate.
@@ -234,15 +251,16 @@ const Status = {
234251};
235252```
236253
237- This library is effectively a programmatic version of these repetitive definitions. It attempts to
238- provide the best of both worlds: string enums with the convenience of built-in enums.
254+ This library is effectively a programmatic version of these repetitive
255+ definitions. It attempts to provide the best of both worlds: string enums with
256+ the convenience of built-in enums.
239257
240258## How It Works
241259
242- This section is not necessary to use this library, but for those curious about how it is
243- implemented, read on. The explanation uses the concepts of index types and mapped types, as
244- described in TypeScript's
245- [ Advanced Types] ( https://www.typescriptlang.org/docs/handbook/advanced-types.html ) page.
260+ This section is not necessary to use this library, but for those curious about
261+ how it is implemented, read on. The explanation uses the concepts of index types
262+ and mapped types, as described in TypeScript's [ Advanced
263+ Types] ( https://www.typescriptlang.org/docs/handbook/advanced-types.html ) page.
246264
247265The relevant type declarations are as follows:
248266
@@ -258,22 +276,24 @@ function Enum<
258276type Enum<T> = T[keyof T];
259277```
260278
261- We are creating a overloaded function named `Enum` and a type named `Enum`, so both can be imported
262- with a single symbol.
279+ We are creating a overloaded function named `Enum` and a type named `Enum`, so
280+ both can be imported with a single symbol.
263281
264- Consider the first overload, which handles the case of variadic arguments representing the enum
265- values. In TypeScript, a string constant is a type (for example, in `const foo = "Hello"`, the
266- variable `foo` is assigned type `"Hello"`). This means that the array
282+ Consider the first overload, which handles the case of variadic arguments
283+ representing the enum values. In TypeScript, a string constant is a type (for
284+ example, in `const foo = "Hello"`, the variable `foo` is assigned type
285+ `"Hello"`). This means that the array
267286
268287``` javascript
269288["RUNNING", "STOPPED"]
270289```
271290
272- can be inferred to have type `("RUNNING" | "STOPPED")[]`, and so when it is passed into a function
273- with the above type signature, the type parameter `V` is thus inferred to be
274- `"RUNNING" | "STOPPED"`. Then the return type `{ [K in V ]: K }` is a mapped type which describes an
275- object whose keys are the types that make up ` V ` and for each such key has a value of the same type
276- as that key. Hence, the type of ` Enum (" RUNNING" , " STOPPED" )` is
291+ can be inferred to have type `("RUNNING" | "STOPPED")[]`, and so when it is
292+ passed into a function with the above type signature, the type parameter `V` is
293+ thus inferred to be `"RUNNING" | "STOPPED"`. Then the return type `{ [K in V ]: K
294+ }` is a mapped type which describes an object whose keys are the types that make
295+ up ` V ` and for each such key has a value of the same type as that key. Hence,
296+ the type of ` Enum (" RUNNING" , " STOPPED" )` is
277297
278298` ` ` javascript
279299// This is a type, not an object literal.
@@ -283,19 +303,20 @@ as that key. Hence, the type of `Enum("RUNNING", "STOPPED")` is
283303}
284304```
285305
286- Next, consider the second overload, which handles the case which takes an object of keys and values,
287- and for the sake of example consider
306+ Next, consider the second overload, which handles the case which takes an object
307+ of keys and values, and for the sake of example consider
288308
289309``` javascript
290310const Status = Enum ({
291311 RUNNING : " running" ,
292312 STOPPED : " stopped" ,
293313});
294314```
295- The second type parameter ` V ` is inferred as ` "running" | "stopped" ` , which forces TypeScript to
296- infer the first type parameter ` T ` as an object whose values are the specific string values that
297- make up ` V ` . Hence, even though ` { RUNNING: "running", "STOPPED": "stopped" } ` would have type
298- ` { RUNNING: string; STOPPED: string; } ` , passing it through ` Enum ` causes its type to be inferred
315+ The second type parameter ` V ` is inferred as ` "running" | "stopped" ` , which
316+ forces TypeScript to infer the first type parameter ` T ` as an object whose
317+ values are the specific string values that make up ` V ` . Hence, even though `{
318+ RUNNING: "running", "STOPPED": "stopped" }` would have type ` { RUNNING: string;
319+ STOPPED: string; }` , passing it through ` Enum` causes its type to be inferred
299320instead as the desired
300321
301322``` javascript
@@ -312,18 +333,46 @@ Next, consider the definition
312333type Enum< T > = T [keyof T ];
313334```
314335
315- This is an index type which describes, for a given keyed type ` T ` , the type obtained by indexing
316- into ` T ` with an arbitrary one of its keys (the syntax ` T[keyof T] ` is meant to evoke the
317- expression ` t[key] ` for some ` key ` in ` t ` ). When passing in an arbitrary key to the object from the
318- previous step, we get a value which might be any one of the object's values, and so its type is thus
319- the union of the types of the object's values. Hence, ` Enum<typeof Enum("RUNNING", "STOPPED")> `
320- evaluates to ` "RUNNING" | "STOPPED" ` , which is what we want.
336+ This is an index type which describes, for a given keyed type ` T ` , the type
337+ obtained by indexing into ` T ` with an arbitrary one of its keys (the syntax
338+ ` T[keyof T] ` is meant to evoke the expression ` t[key] ` for some ` key ` in ` t ` ).
339+ When passing in an arbitrary key to the object from the previous step, we get a
340+ value which might be any one of the object's values, and so its type is thus the
341+ union of the types of the object's values. Hence, `Enum<typeof Enum("RUNNING",
342+ "STOPPED")>` evaluates to ` "RUNNING" | "STOPPED"`, which is what we want.
343+
344+ ## Advantages over native string enums
345+
346+ With the addition of native string enums in TypeScript 2.4, this library will be
347+ unnecessary for most users. There are still a few niche reasons why users may
348+ still prefer to use this library.
349+
350+ * This library provides several helper functions which cannot easy be
351+ implemented for native enums. Of these, ` Enum.isType() ` will likely be the
352+ most useful.
353+ * Defining a native string enum involves a bit of repetition, as each value must
354+ be written twice:
355+ ``` ts
356+ enum Color {
357+ RED = " RED" ,
358+ GREEN = " GREEN" ,
359+ BLUE = " BLUE"
360+ }
361+ ```
362+ vs
363+ ``` ts
364+ const Color = Enum (" RED" , " GREEN" , " BLUE" );
365+ type Color = Enum <typeof Color >;
366+ ```
367+ If there are many values, it may be desirable to avoid the repetition and
368+ corresponding possibility for typos.
321369
322370## Acknowledgements
323371
324- This libary is heavily inspired by posts in
325- [ this thread] ( https://github.com/Microsoft/TypeScript/issues/3192 ) . In particular, credit goes to
326- users ** [ @igrayson ] ( https://github.com/igrayson ) ** , ** [ @nahuel ] ( https://github.com/nahuel ) ** ,
327- and ** [ @kourge ] ( https://github.com/kourge ) ** .
372+ This libary is heavily inspired by posts in [ this
373+ thread] ( https://github.com/Microsoft/TypeScript/issues/3192 ) . In particular,
374+ credit goes to users ** [ @igrayson ] ( https://github.com/igrayson ) ** ,
375+ ** [ @nahuel ] ( https://github.com/nahuel ) ** , and
376+ ** [ @kourge ] ( https://github.com/kourge ) ** .
328377
329378Copyright © 2017 David Philipson
0 commit comments