-
Notifications
You must be signed in to change notification settings - Fork 134
/
01-apiMapping.exercise.ts
235 lines (215 loc) Β· 6.83 KB
/
01-apiMapping.exercise.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/**
* π§βπ» We're building a scheduling app for a large HR company.
* We built our backends and frontends separately, but there
* was a bit of miscommunication about the enums we would use
* to represent different bookings. The backend is now using
* SCREAMING_SNAKE_CASE, but we're using camelCase.
*
* So we've made a map to convert from the backend version of
* ProgramMode - GROUP, ANNOUNCEMENT etc - to the frontend
* version - group, announcement etc.
*/
export const programModeEnumMap = {
// ^ π
GROUP: "group",
ANNOUNCEMENT: "announcement",
ONE_ON_ONE: "1on1",
SELF_DIRECTED: "selfDirected",
PLANNED_ONE_ON_ONE: "planned1on1",
PLANNED_SELF_DIRECTED: "plannedSelfDirected",
} as const;
/**
* π Hover over programModeEnumMap. It should display
* as an object with LOTS of readonly properties, all inferred
* as their literal type ("group", "announcement" etc).
*/
export type ProgramMap = typeof programModeEnumMap;
/** ^ π
*
* π Hover over ProgramMap - it should be exactly the same
* display as when you hovered over programModeEnumMap
*/
export type Program = ProgramMap[keyof ProgramMap];
/** ^ π
*
* π Program is being inferred as a union type of all of
* the frontend enums. Interesting.
*/
// β¬οΈ π
export type IndividualProgram = ProgramMap[
| "ONE_ON_ONE"
| "SELF_DIRECTED"
| "PLANNED_ONE_ON_ONE"
| "PLANNED_SELF_DIRECTED"];
/**
* π IndividualProgram is all of the enums, EXCEPT for
* the ones with the keys of GROUP and ANNOUNCEMENT
*/
export type GroupProgram = ProgramMap["GROUP"];
/** ^ π
*
* π GroupProgram is just the "group" member of the union.
*/
/**
* π OK, we know what each section does. Now let's recreate
* it. Comment out all of the code EXCEPT for the
* programModeEnumMap.
*/
/**
* π‘ The remaining code is just JavaScript - except for a
* little extra annotation: "as const"
*
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
*
* Let's investigate what this does.
*/
/**
* π Remove the "as const" annotation:
*
* export const programModeEnumMap = {
* GROUP: "group",
* ANNOUNCEMENT: "announcement",
* ONE_ON_ONE: "1on1",
* SELF_DIRECTED: "selfDirected",
* PLANNED_ONE_ON_ONE: "planned1on1",
* PLANNED_SELF_DIRECTED: "plannedSelfDirected",
* };
*
* π Now, hover over programModeEnumMap. You'll
* notice that each of the properties are now inferred
* as string, not their literal types.
*
* Why is this? It's because objects in JavaScript are
* mutable by default.
*
* π΅οΈββοΈ Try reassigning one of the properties of
* programModeEnumMap:
*
* programModeEnumMap.GROUP = 'some-other-thing';
*
* You'll see that it doesn't throw an error.
*/
/**
* π Add the as const annotation back in.
*
* βοΈ You'll see that the line we wrote above is now erroring!
*
* Cannot assign to 'GROUP' because it is a read-only property.
*
* π Remove that line of code to clear the error.
*
* π‘ This is good! We don't want our config objects to be mutable.
* You can also achieve this with Object.freeze, but this only
* works one level deep on objects. 'as const' works recursively
* down the entire object.
*/
/**
* π‘ We now want to derive our enum type from this object
* so that we can use it in the rest of our application.
*
* To do that, we're going to need to pull it from the
* runtime world into the type world.
*
* We'll need to use typeof for that:
*
* https://www.typescriptlang.org/docs/handbook/2/typeof-types.html
*
* π Declare a new type called ProgramMap, which uses typeof
* on the programModeEnumMap:
*
* type ProgramMap = typeof programModeEnumMap;
*/
/**
* π‘ All this does is pull the inferred type of programModeEnumMap
* into the type world, so we can manipulate it using TS syntax.
*
* π Let's declare a new type, GroupProgram, and make it the property
* 'GROUP' of ProgramMap:
*
* type GroupProgram = ProgramMap["GROUP"];
* ^ π
*
* π Hover over GroupProgram - it should be inferred as "group"
*/
/**
* π‘ "as const" and typeof are a powerful combination, because
* they let us do really clever things with config objects.
*
* Without "as const", this inference would look very different.
*
* π΅οΈββοΈ Try removing "as const" again. When hovering over GroupProgram,
* it will now be inferred as a string! So "as const" is crucial
* to deriving types from config objects. Add it back in again.
*/
/**
* π‘ Let's now recreate the IndividualProgram type, which represents
* all the members of the enum that are for individuals only.
*
* We want to create a union type of all the possible individual
* programs. To do that, you can pass in a union type to the property
* access!
*
* π Create a new type called IndividualProgram. Make it a property
* access on ProgramMap, but pass in ONE_ON_ONE, SELF_DIRECTED,
* PLANNED_ONE_ON_ONE and PLANNED_SELF_DIRECTED as a union.
*
* β¬οΈ π
* export type IndividualProgram = ProgramMap[
* | "ONE_ON_ONE"
* | "SELF_DIRECTED"
* | "PLANNED_ONE_ON_ONE"
* | "PLANNED_SELF_DIRECTED"];
*
* π Hover over IndividualProgram - it should be a union of
* "1on1" | "selfDirected" | "planned1on1" | "plannedSelfDirected"
*
* π΅οΈββοΈ Try altering some of the members of the union you're passing in,
* noticing how IndividualProgram also gets altered.
*/
/**
* π‘ This concept, that you can access objects via a union type to
* RETURN a union type, is critical for understanding complex types.
*
* π‘ We still don't have a union type for ALL of the members of
* the enum. To do that, we'd need to pass a union of ALL the keys
* of ProgramMap BACK to ProgramMap.
*
* For that, we're going to use keyof:
*
* https://www.typescriptlang.org/docs/handbook/2/keyof-types.html
*/
/**
* π Let's create a type called BackendProgram. Assign it to
* keyof ProgramMap:
*
* type BackendProgram = keyof ProgramMap;
* ^ π
*
* π Hover over BackendProgram. You should see that it's a union
* of all of the keys in programModeEnumMap.
*/
/**
* π Create a new type called Program, which accesses ProgramMap with
* ALL of ProgramMap's keys:
*
* type Program = ProgramMap[BackendProgram];
* ^ π
*
* π You'll see that Program is typed as a union of all of the
* members of the frontend enum:
*
* "group" | "announcement" | "1on1" | "selfDirected"
* | "planned1on1" | "plannedSelfDirected"
*/
/**
* π‘ You can use this pattern: Obj[keyof Obj] as a kind of
* Object.values for the type world.
*/
/**
* π‘ Well done! We've covered 'as const', 'keyof', and accessing
* index types via unions.
*
* π΅οΈββοΈ Try experimenting with programModeEnumMap, changing the keys
* and values to see what errors, or what changes in each of
* the derived types.
*/