Skip to content

Commit ec9a523

Browse files
authored
Merge pull request #10 from dphilipson/docs/object-parameters-in-readme
Add docs for differing key-values
2 parents 512de2d + b0d1a3c commit ec9a523

File tree

1 file changed

+73
-20
lines changed

1 file changed

+73
-20
lines changed

README.md

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

6487
Enums 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
133156
described 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
152178
variable `foo` is assigned type `"Hello"`). This means that the array
153179

154180
``` javascript
155181
["RUNNING", "STOPPED"]
156182
```
157183

158184
can 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
188231
the union of the types of the object's values. Hence, `Enum<typeof Enum("RUNNING", "STOPPED")>`
189232
evaluates 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

193245
This 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

197250
Copyright © 2017 David Philipson

0 commit comments

Comments
 (0)