@@ -59,6 +59,29 @@ function saySomethingAboutState(state: State) {
5959}
6060```
6161
62+ Instead of a list of values, an object may be passed instead if it is desired that the string values
63+ be different from the constant names. This also has the advantage of allowing JSDoc comments to be
64+ specified on individual values. For example:
65+
66+ ``` javascript
67+ export const Status = Enum ({
68+ /**
69+ * Everything is fine.
70+ *
71+ * Hovering over Status.RUNNING in an IDE will show this comment.
72+ */
73+ RUNNING : " running" ,
74+
75+ /**
76+ * All is lost.
77+ */
78+ STOPPED : " stopped" ,
79+ });
80+ export type Status = Enum < typeof Status > ;
81+
82+ console .log (Status .RUNNING ); // -> "running"
83+ ```
84+
6285## Motivation
6386
6487Enums are useful for cleanly specifying a type that can take one of a few specific values.
@@ -133,45 +156,65 @@ implemented, read on. The explanation uses the concepts of index types and mappe
133156described in TypeScript's
134157[ Advanced Types] ( https://www.typescriptlang.org/docs/handbook/advanced-types.html ) page.
135158
136- The entire source of this library is
159+ The relevant type declarations are as follows:
137160
138161``` javascript
139- export function Enum<V extends string>(... values : V []): { [K in V ]: K } {
140- const result: any = {};
141- values .forEach ((value ) => result[value] = value);
142- return result;
143- }
162+ function Enum<V extends string>(... values : V []): { [K in V ]: K };
163+ function Enum<
164+ T extends { [_: string]: V },
165+ V extends string
166+ >(definition : T ): T;
167+
168+ ...
144169
145- export type Enum < T > = T [keyof T ];
170+ type Enum<T> = T[keyof T];
146171```
147172
148- We are creating a function named ` Enum ` and a type named ` Enum ` , so both can be imported with a
149- single symbol.
173+ We are creating a overloaded function named `Enum` and a type named `Enum`, so both can be imported
174+ with a single symbol.
150175
151- In TypeScript, a string constant is a type (for example, in ` const foo = "Hello" ` , the
176+ Consider the first overload, which handles the case of variadic arguments representing the enum
177+ values. In TypeScript, a string constant is a type (for example, in `const foo = "Hello"`, the
152178variable `foo` is assigned type `"Hello"`). This means that the array
153179
154180``` javascript
155181["RUNNING", "STOPPED"]
156182```
157183
158184can be inferred to have type `("RUNNING" | "STOPPED")[]`, and so when it is passed into a function
159- with type signature
185+ with the above type signature, the type parameter `V` is thus inferred to be
186+ `"RUNNING" | "STOPPED"`. Then the return type `{ [K in V ]: K }` is a mapped type which describes an
187+ object whose keys are the types that make up ` V ` and for each such key has a value of the same type
188+ as that key. Hence, the type of ` Enum (" RUNNING" , " STOPPED" )` is
160189
161190` ` ` javascript
162- function Enum<V extends string>(... values : V []): { [K in V ]: K }
191+ // This is a type, not an object literal.
192+ {
193+ RUNNING : " RUNNING" ;
194+ STOPPED : " STOPPED" ;
195+ }
163196```
164197
165- the type parameter ` V ` is thus inferred to be ` "RUNNING" | "STOPPED" ` . Then the return type
166- ` { [K in V]: K } ` is a mapped type which describes an object whose keys are the types that
167- make up ` V ` and for each such key has a value of the same type as that key. Hence, the type of
168- ` Enum("RUNNING", "STOPPED") ` is
198+ Next, consider the second overload, which handles the case which takes an object of keys and values,
199+ and for the same of example consider
169200
170201``` javascript
171- // This is a type, not an object literal.
202+ const Status = Enum ({
203+ RUNNING : " running" ,
204+ STOPPED : " stopped" ,
205+ });
206+ ```
207+ The second type parameter ` V ` is inferred as ` "running" | "stopped" ` , which forces TypeScript to
208+ infer the first type parameter ` T ` as an object whose values are the specific string values that
209+ make up ` V ` . Hence, even though ` { RUNNING: "running", "STOPPED": "stopped" } ` would have type
210+ ` { RUNNING: string; STOPPED: string; } ` , passing it through ` Enum ` causes its type to be inferred
211+ instead as the desired
212+
213+ ``` javascript
214+ // Type, not object literal.
172215{
173- RUNNING : " RUNNING " ;
174- STOPPED : " STOPPED " ;
216+ RUNNING : " running " ;
217+ STOPPED : " stopped " ;
175218}
176219```
177220
@@ -188,10 +231,20 @@ previous step, we get a value which might be any one of the object's values, and
188231the union of the types of the object's values. Hence, ` Enum<typeof Enum("RUNNING", "STOPPED")> `
189232evaluates to ` "RUNNING" | "STOPPED" ` , which is what we want.
190233
234+ By contrast, the type definition for the case which takes an object of keys and values is
235+
236+ ``` javascript
237+ function Enum<
238+ T extends { [_: string]: V },
239+ V extends string
240+ >(definition : T ): T
241+ ```
242+
191243## Acknowledgements
192244
193245This libary is heavily inspired by posts in
194246[this thread](https:// github.com/Microsoft/TypeScript/issues/3192). In particular, credit goes to
195- users ** [ @igrayson ] ( https://github.com/igrayson ) ** and ** [ @nahuel ] ( https://github.com/nahuel ) ** .
247+ users **[@igrayson](https:// github.com/igrayson)**, **[@nahuel](https://github.com/nahuel)**,
248+ and **[@kourge](https:// github.com/kourge).
196249
197250Copyright © 2017 David Philipson
0 commit comments