-
Notifications
You must be signed in to change notification settings - Fork 0
/
02-complex-types.nnb
214 lines (214 loc) · 14.7 KB
/
02-complex-types.nnb
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
{
"cells": [
{
"language": "markdown",
"source": [
"# Complex types\n\nUiteraard zijn er meer dan alleen de basis types die we in de vorige notebook hebben gezien. In deze notebook gaan we kijken naar de complexere types die TypeScript kent.\n\n> Onderstaande operators kunnen vaak werken met interfaces als argument, maar ze returnen nadien een type.\n\n## Intersection types\n\nOm types te combineren moet je gebruik maken van de `&` operator, dit heet **intersection types**. De types worden hierbij samengevoegd tot één type. Het is hierbij verplicht dat variabelen van dit type aan alle types voldoen, m.a.w. het moet alle properties uit de types bevatten."
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Book = {\n title: string;\n author: string;\n};\n\ntype BookExtension = Book & { isbn: string; };\nconst book: BookExtension = {\n title: 'Introducing MLOps',\n author: 'Mark Treveil & the Dataiku Team',\n isbn: '9781492083290'\n};"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Union types\n\nJe kan ook types combineren met de `|` operator, dit heet **union types**. De types worden hierbij niet samengevoegd, het is ofwel het ene ofwel het andere type, ofwel een combinatie van beide. Het is dus niet verplicht om aan alle types te voldoen.\n"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Member = {\n name: string;\n age: number;\n};\n\ntype Email = {\n email: string;\n};\n\ntype MemberExtension = Member | Email;\n\nconst member: MemberExtension = {\n name: 'Thomas Aelbrecht',\n age: 25\n};\nconst member2: MemberExtension = {\n name: 'Thomas Aelbrecht',\n age: 25,\n email: '[email protected]',\n};\nconst member3: MemberExtension = {\n email: '[email protected]',\n};"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Zet je de union operator helemaal vooraan het type, dan moet het één van de types zijn, maar niet alle types. Dit heet **discriminated unions**."
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type NetworkLoadingState = {\n state: \"loading\";\n};\ntype NetworkFailedState = {\n state: \"failed\";\n code: number;\n};\ntype NetworkSuccessState = {\n state: \"success\";\n response: {\n title: string;\n duration: number;\n summary: string;\n };\n};\n// Een van de drie, niet allemaal\ntype NetworkState =\n | NetworkLoadingState\n | NetworkFailedState\n | NetworkSuccessState;"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"In dit voorbeeld is het property `state` gedeeld. TypeScript kan dit property gebruiken om type inference te doen om te bepalen welk type gebruikt wordt. Zo krijg je in bv. if-structuren de juiste code completion en type checking."
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Utility types\n\nTypeScript heeft ook heel wat [utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html). Dit zijn types die je kan gebruiken om andere types te maken. De meest gebruikte utility types zijn:\n\n* `Partial<Type>`: maakt een type optioneel\n* `Omit<Type, Keys>`: verwijdert een of meerdere properties van een type\n* `Record<Keys, Types>`: maakt een type voor een object met properties met naam volgens `Keys` en type volgens `Types`\n* `Pick<Type, Keys>`: haalt een of meerdere properties (`Keys`) op uit een type (`Type`)\n* ..."
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type MyExample = {\n a: number;\n b: string;\n};\n\ntype WithoutB = Omit<MyExample, 'b'>;\ntype OptionalMyExample = Partial<MyExample>;\n\ntype PersonKeys = 'firstName' | 'lastName' | 'email';\ntype Person = Record<PersonKeys, string>;\n// is gelijk aan:\n// type Person = {\n// firstName: string;\n// lastName: string;\n// email: string;\n// };\n\ntype OnlyEmail = Pick<Person, 'email'>;\ntype FullName = Pick<Person, 'firstName' | 'lastName'>;"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## Type manipulation\n\nTypeScript biedt ook manieren om als het ware types te creëren op basis van andere types, m.a.w. we manipuleren types. Voorbeelden zijn:\n\n- keyof\n- typeof\n- conditional types\n- ...\n\nEr is veel meer te vinden in de docs: <https://www.typescriptlang.org/docs/handbook/2/types-from-types.html>.\n\n### keyof\n\nDe `keyof` type operator neemt een object type als parameter en geeft een `string` of `number` type terug. Dit type bevat alle keys van het object type.\n"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Point = { x: number; y: number };\ntype P = keyof Point; // \"x\" | \"y\"\n\ntype Arrayish = { [n: number]: unknown };\ntype A = keyof Arrayish; // number\n\ntype Mapish = { [k: string]: boolean };\ntype M = keyof Mapish; // string | number\n\n// "
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Als `keyof` gebruikt wordt op een type met `string` als index key, dan is het return type `string | number`. Dit komt omdat JS automatisch `number` keys omzet naar `string` keys. `obj[0]` is dus gelijk aan `obj[\"0\"]`.\n\n### typeof\n\nJavaScript heeft al een `typeof` operator die je kan gebruiken bij *expressies*. Simpel gezegd: aan de rechterkant van de `=`. Deze operator geeft het type van de expressie terug. TypeScript heeft ook een `typeof` operator, maar deze werkt op *types*.\n"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"const s = \"Codifly\";\nlet n: typeof s; // string"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"`typeof` heeft weinig nut bij simpele types, het wordt heel handig bij complexe types. Bijvoorbeeld bij het maken van een type voor een functie. Je kan dan de return type van de functie opvragen met `typeof`."
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Add = (a: number, b: number) => number;\ntype Antwoord = ReturnType<Add>; // number\n\nfunction add(a: number, b: number): number {\n return a + b;\n}\n// werkt niet want add is een value, geen type\n// type AnderAntwoord = ReturnType<add>;\ntype AnderAntwoord = ReturnType<typeof add>; // number"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"### conditional types\n\nConditional types zijn types met een if-structuur waarbij we de syntax van de ternaire operator gebruiken: `voorwaarde ? doe dit indien true : doe dit indien false`.\n"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Shape = {\n area(): number;\n};\n\ntype Circle = Shape & {\n getRadius(): number;\n};\n\ntype Example1 = Circle extends Shape ? string : number; // string\n\ntype Example2 = RegExp extends Circle ? string : number; // number\n"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Lees verder op <https://www.typescriptlang.org/docs/handbook/2/conditional-types.html> vanaf de `createLabel` functie."
],
"outputs": []
},
{
"language": "markdown",
"source": [
"### satisfies operator\n\nDe `satisfies` operator is een nieuwe feature in TypeScript 4.9. Het is een operator die je kan gebruiken om te checken of een type voldoet aan een ander type. Het is een soort van `instanceof` operator, maar dan voor types. Het laat toe om via type inference het meest specifieke type te gebruiken terwijl je toch controleert of het type voldoet aan een bepaald (ander) type.\n\n> Bekijk het voorbeeld op <https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator>"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"### infer operator\n\n"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"De `infer` operator kan je binnen conditional types gebruiken om het type af te leiden om te gebruiken in de `true` of `false` kant van de conditional type. Op die manier hoef je geen indexed access te gebruiken om het type te achterhalen.\n\nKijk naar onderstaand voorbeeld:"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Dit type kan gebruikt worden om eender welk type om te vormen naar iets zonder array. Een `string` blijft dus een `string`, een `number[]` wordt `number`.\n\nJe zou ook een type kunnen schrijven om de returnwaarde van een functie te achterhalen. Dit is echter niet nodig, want TypeScript heeft hiervoor reeds een type: `ReturnType`. Toch een voorbeeld:"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type GetReturnType<Type> = Type extends (...args: never[]) => infer Return\n ? Return\n : never;\n \ntype Num = GetReturnType<() => number>;\n// type Num = number\n\ntype Str = GetReturnType<(x: string) => string>;\n// type Str = string\n\ntype Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;\n// type Bools = boolean[]"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"## InterfaceSelection\n\nNu weten we net genoeg om het `InterfaceSelection` type te begrijpen. Het idee van dit type is een geneste pick:\n\n```typescript\nInterfaceSelection<TsType, { fieldWeWantToPickFromType: void, anotherFieldFromType: { nestedField1: { nestedField2: void } } }>\n```\n\nWe willen dus eigenlijk een type met naam `InterfaceSelection` met als argumenten:\n\n- een TypeScript type (de bron waaruit we picken),\n- iets wat beschrijft hoe en wat we willen picken (het patroon).\n\nWe definiëren eerst het patroon:"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"// T = waaruit gepicked wordt\ntype InterfaceSelectionPattern<T> =\n // ofwel void = geval waar we stoppen met picken/recursie\n | void\n // ofwel een object met de keys van T en als value een InterfaceSelectionPattern\n | (\n // zijn we een array?\n [T & {}] extends [(infer E)[]]\n // dan simpel een recursief patroon\n ? InterfaceSelectionPattern<E>\n // anders moeten we de keys van T picken met een recursief patroon\n : {\n [K in keyof (T & {})]?: InterfaceSelectionPattern<(T & {})[K]>;\n }\n );"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"Nu we het patroon hebben, kunnen we de geneste picker maken."
],
"outputs": []
},
{
"language": "typescript",
"source": [
"// T = waaruit gepicked wordt\n// P = het patroon waarmee we picken, vanaar de extends\ntype InterfaceSelection<T, P extends InterfaceSelectionPattern<T>> =\n // als P void is, dan is het resultaat T - we stoppen met picken\n [P,] extends [void]\n ? T\n // anders exact één van de volgende:\n // als T null is, dan null - anders ga verder\n : | (null extends T ? null : never)\n // als T undefined is, dan undefined - anders ga verder\n | (undefined extends T ? undefined : never)\n // anders:\n | (\n // zijn we een array?\n [T & {}] extends [(infer E)[]]\n // dan simpel een recursief patroon op het type van de elementen in de array (= E)\n ? InterfaceSelection<E, P & InterfaceSelectionPattern<E>>[]\n // anders een recursief patroon op de keys van het object T\n // de & {} is nodig aangezien we niet zeker zijn of T nog een object is\n : {\n [K in keyof ((T | P) & {})]: InterfaceSelection<\n (T & {})[K],\n (P & {})[K] & InterfaceSelectionPattern<(T & {})[K]>\n >;\n }\n );"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"En dit kan als volgt gebruikt worden:"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"type Animal = {\n name: string;\n age: number;\n};\n\ntype Zoo = {\n animals: Animal[];\n zookeeper: {\n firstName: string;\n lastName: string;\n phone: string;\n };\n};\n\nconst zoo: Zoo = {\n animals: [\n {\n name: 'Zebra',\n age: 5,\n },\n {\n name: 'Giraffe',\n age: 10,\n },\n ],\n zookeeper: {\n firstName: 'John',\n lastName: 'Doe',\n phone: '123-456-7890',\n }\n};\n\ntype ZooKeeper = InterfaceSelection<Zoo, { zookeeper: void }>;\n\nconst zooKeeper: ZooKeeper = {\n zookeeper: zoo.zookeeper,\n};\nconsole.log(zooKeeper);\n\ntype AnimalNames = InterfaceSelection<Zoo, { animals: { name: void } }>;\n\nconst animalNames: AnimalNames = {\n animals: zoo.animals.map((animal) => ({ name: animal.name })),\n};\nconsole.log(animalNames);\n\n"
],
"outputs": []
}
]
}