"Nasz park to niezwykle złożony organizm, który stale ewoluuje. Dzisiaj potrzebujemy rejestru wszystkich zagrożeń, jutro będziemy potrzebować listy niezbędnych zapasów, a pojutrze - pełnego spisu dinozaurów wraz z ich statystykami," wyjaśnia John Hammond, założyciel i dyrektor Parku Jurajskiego. "Za każdym razem musimy przetwarzać te same podstawowe dane, ale w różnych konfiguracjach i formatach."
W świecie programowania często stajemy przed podobnym wyzwaniem - przetwarzaniem istniejących typów danych w nowe formy, które lepiej pasują do konkretnych wymagań. TypeScript oferuje eleganckie rozwiązanie tego problemu w postaci typów mapowanych (mapped types), które pozwalają na transformację istniejących typów w sposób systematyczny i typowo bezpieczny.
Typy mapowane w TypeScript pozwalają tworzyć nowe typy na podstawie istniejących poprzez przekształcenie właściwości oryginalnego typu według określonego wzorca. Działa to podobnie do metody
map() dla tablic w JavaScript, ale na poziomie typów - zamiast transformować wartości w tablicy, transformujemy właściwości w typie.Podstawowa składnia typu mapowanego wygląda następująco:
1type MappedType<T> = {
2 [K in keyof T]: TransformedType
3}W tym schemacie:
T to typ źródłowyK in keyof T iteruje przez wszystkie klucze typu TTransformedType to nowy typ, który zostanie przypisany do każdej właściwościJednym z najprostszych przykładów typu mapowanego jest
Readonly<T>, który tworzy nowy typ, w którym wszystkie właściwości źródłowego typu T są tylko do odczytu:1type Readonly<T> = {
2 readonly [K in keyof T]: T[K];
3};Zastosujmy to do naszego przykładu z Parkiem Jurajskim:
1// Definicja typu dla dinozaura
2interface Dinosaur {
3 id: string;
4 name: string;
5 species: string;
6 age: number;
7 weight: number;
8 diet: "carnivore" | "herbivore" | "omnivore";
9 enclosure: string;
10}
11
12// Tworzenie niemodyfikowalnego typu dinozaura
13type ReadonlyDinosaur = Readonly<Dinosaur>;
14
15// Użycie
16const trex: ReadonlyDinosaur = {
17 id: "TREX-01",
18 name: "Rexy",
19 species: "Tyrannosaurus Rex",
20 age: 7,
21 weight: 7500,
22 diet: "carnivore",
23 enclosure: "Paddock 9"
24};
25
26// To nie skompiluje się - właściwości są tylko do odczytu
27// trex.name = "Rex"; // Error: Cannot assign to 'name' because it is a read-only propertyTypy mapowane pozwalają nie tylko na transformację typów właściwości, ale także na dodawanie, usuwanie lub modyfikowanie modyfikatorów, takich jak
readonly czy ? (opcjonalność).1// Tworzy typ, gdzie wszystkie właściwości są opcjonalne
2type Partial<T> = {
3 [K in keyof T]?: T[K];
4};
5
6// Użycie
7type PartialDinosaur = Partial<Dinosaur>;
8
9// Obiekt może zawierać tylko część właściwości
10const newDino: PartialDinosaur = {
11 species: "Velociraptor",
12 diet: "carnivore"
13};Możemy również usunąć modyfikatory, używając znaku
-:1// Tworzy typ, gdzie wszystkie właściwości są wymagane
2type Required<T> = {
3 [K in keyof T]-?: T[K];
4};
5
6// Załóżmy, że mamy typ z opcjonalnymi właściwościami
7interface OptionalDinosaur {
8 id: string;
9 name: string;
10 species: string;
11 age?: number;
12 weight?: number;
13 diet?: "carnivore" | "herbivore" | "omnivore";
14 enclosure?: string;
15}
16
17// Tworzymy typ, gdzie wszystkie właściwości są wymagane
18type RequiredDinosaur = Required<OptionalDinosaur>;
19
20// To nie skompiluje się - brakuje wymaganych właściwości
21// const incompleteDino: RequiredDinosaur = {
22// id: "VEL-02",
23// name: "Blue",
24// species: "Velociraptor"
25// };Oprócz modyfikowania modyfikatorów właściwości, możemy również transformować typy samych właściwości:
1// Transformuje wszystkie właściwości na stringi
2type StringifiedDinosaur = {
3 [K in keyof Dinosaur]: string;
4};
5
6const strDino: StringifiedDinosaur = {
7 id: "TREX-01",
8 name: "Rexy",
9 species: "Tyrannosaurus Rex",
10 age: "7", // Musi być string, nie number
11 weight: "7500", // Musi być string, nie number
12 diet: "carnivore",
13 enclosure: "Paddock 9"
14};Możemy łączyć typy mapowane z typami warunkowymi, aby selektywnie transformować właściwości w zależności od ich typu:
1// Transformuje tylko numeryczne właściwości na stringi
2type StringifyNumericProps<T> = {
3 [K in keyof T]: T[K] extends number ? string : T[K];
4};
5
6// Użycie
7type DinosaurWithStringifiedNumbers = StringifyNumericProps<Dinosaur>;
8
9const mixedDino: DinosaurWithStringifiedNumbers = {
10 id: "TREX-01",
11 name: "Rexy",
12 species: "Tyrannosaurus Rex",
13 age: "7", // Musi być string (oryginalnie number)
14 weight: "7500", // Musi być string (oryginalnie number)
15 diet: "carnivore", // Wciąż unia literałów
16 enclosure: "Paddock 9"
17};1// Definicja typów dla parametrów dinozaura
2interface DinosaurParameters {
3 maxSpeed: number; // km/h
4 length: number; // metry
5 height: number; // metry
6 intelligence: number; // skala 1-10
7 aggressionLevel: number; // skala 1-10
8 socialBehavior: "solitary" | "pair" | "pack";
9}
10
11// Tworzymy typ konfiguracji, gdzie wszystkie parametry są modyfikowalne
12type DinosaurParametersConfig = {
13 [K in keyof DinosaurParameters]: {
14 min: number;
15 max: number;
16 default: DinosaurParameters[K];
17 description: string;
18 };
19};
20
21// Użycie
22const velociraptorConfig: DinosaurParametersConfig = {
23 maxSpeed: {
24 min: 30,
25 max: 70,
26 default: 50,
27 description: "Maksymalna prędkość biegu w km/h"
28 },
29 length: {
30 min: 1.5,
31 max: 2.5,
32 default: 2.0,
33 description: "Długość ciała w metrach"
34 },
35 height: {
36 min: 0.5,
37 max: 1.0,
38 default: 0.8,
39 description: "Wysokość w metrach"
40 },
41 intelligence: {
42 min: 1,
43 max: 10,
44 default: 9,
45 description: "Poziom inteligencji w skali 1-10"
46 },
47 aggressionLevel: {
48 min: 1,
49 max: 10,
50 default: 8,
51 description: "Poziom agresji w skali 1-10"
52 },
53 socialBehavior: {
54 min: 0,
55 max: 2,
56 default: "pack",
57 description: "Zachowania społeczne: samotnik, para lub stado"
58 }
59};1// Definicja modelu danych
2interface DinosaurRecord {
3 id: string;
4 name: string;
5 species: string;
6 age: number;
7 diet: "carnivore" | "herbivore" | "omnivore";
8 healthStatus: "healthy" | "sick" | "critical";
9 lastCheckup: Date;
10}
11
12// Typ formularza z walidacją
13type DinosaurFormValidation = {
14 [K in keyof DinosaurRecord]: {
15 required: boolean;
16 validator?: (value: DinosaurRecord[K]) => boolean;
17 errorMessage?: string;
18 };
19};
20
21// Implementacja
22const dinoFormValidation: DinosaurFormValidation = {
23 id: {
24 required: true,
25 validator: (id) => /^DINO-d{3}$/.test(id),
26 errorMessage: "ID musi być w formacie DINO-XXX, gdzie X to cyfra"
27 },
28 name: {
29 required: true,
30 validator: (name) => name.length >= 2 && name.length <= 50,
31 errorMessage: "Imię musi mieć od 2 do 50 znaków"
32 },
33 species: {
34 required: true
35 },
36 age: {
37 required: true,
38 validator: (age) => age > 0 && age < 30,
39 errorMessage: "Wiek musi być między 0 a 30 lat"
40 },
41 diet: {
42 required: true
43 },
44 healthStatus: {
45 required: true
46 },
47 lastCheckup: {
48 required: true,
49 validator: (date) => date <= new Date(),
50 errorMessage: "Data ostatniego badania nie może być w przyszłości"
51 }
52};1// Typ identyfikujący, które pola zostały zmienione
2type Changed<T> = {
3 [K in keyof T]: boolean;
4};
5
6// Funkcja śledząca zmiany w obiekcie
7function trackChanges<T>(original: T, updated: T): Changed<T> {
8 const result = {} as Changed<T>;
9
10 for (const key in original) {
11 result[key] = original[key] !== updated[key];
12 }
13
14 return result;
15}
16
17// Przykład użycia
18const originalDino: Dinosaur = {
19 id: "TREX-01",
20 name: "Rexy",
21 species: "Tyrannosaurus Rex",
22 age: 7,
23 weight: 7500,
24 diet: "carnivore",
25 enclosure: "Paddock 9"
26};
27
28const updatedDino: Dinosaur = {
29 id: "TREX-01",
30 name: "Rexy",
31 species: "Tyrannosaurus Rex",
32 age: 8, // Zmieniono
33 weight: 8000, // Zmieniono
34 diet: "carnivore",
35 enclosure: "Paddock 10" // Zmieniono
36};
37
38const changes = trackChanges(originalDino, updatedDino);
39console.log(changes);
40// Wynik: { id: false, name: false, species: false, age: true, weight: true, diet: false, enclosure: true }Możemy użyć typów mapowanych razem z typami warunkowymi, aby filtrować klucze obiektu na podstawie typów ich wartości:
1// Wybiera klucze, których wartości są określonego typu
2type FilterKeys<T, U> = {
3 [K in keyof T]: T[K] extends U ? K : never;
4}[keyof T];
5
6// Wybiera podzbiór obiektu na podstawie typu wartości
7type SubType<T, U> = {
8 [K in FilterKeys<T, U>]: T[K];
9};
10
11// Przykładowe użycie: wybieranie tylko numerycznych właściwości
12type NumericDinosaurProps = SubType<Dinosaur, number>;
13// Wynik: { age: number; weight: number; }
14
15// Użycie
16const dinoStats: NumericDinosaurProps = {
17 age: 7,
18 weight: 7500
19};Możemy również tworzyć rekursywne typy mapowane, które transformują zagnieżdżone struktury:
1// Rekursywnie tworzy tylko do odczytu
2type DeepReadonly<T> = {
3 readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
4};
5
6// Zagnieżdżona struktura danych
7interface ParkSystem {
8 name: string;
9 status: "online" | "offline" | "maintenance";
10 subsystems: {
11 security: {
12 cameras: number;
13 fences: {
14 powered: boolean;
15 voltage: number;
16 }[];
17 };
18 power: {
19 mainGenerator: {
20 output: number;
21 fuel: number;
22 };
23 backupGenerator: {
24 output: number;
25 fuel: number;
26 };
27 };
28 };
29}
30
31// Tworzenie głęboko niemutowalnej wersji
32type ReadonlyParkSystem = DeepReadonly<ParkSystem>;
33
34const parkSystem: ReadonlyParkSystem = {
35 name: "Jurassic Park Control System",
36 status: "online",
37 subsystems: {
38 security: {
39 cameras: 48,
40 fences: [
41 { powered: true, voltage: 10000 },
42 { powered: true, voltage: 10000 }
43 ]
44 },
45 power: {
46 mainGenerator: {
47 output: 8000,
48 fuel: 75
49 },
50 backupGenerator: {
51 output: 3000,
52 fuel: 100
53 }
54 }
55 }
56};
57
58// Nie można modyfikować żadnej właściwości, nawet zagnieżdżonej
59// parkSystem.status = "offline"; // Error
60// parkSystem.subsystems.security.cameras = 50; // Error
61// parkSystem.subsystems.security.fences[0].powered = false; // ErrorPoniżej znajduje się zaawansowany przykład wykorzystujący typy mapowane do stworzenia elastycznego systemu zarządzania zasobami Parku Jurajskiego:
1// --- Definicje typów bazowych ---
2
3// Bazowy interfejs dla wszystkich zasobów parku
4interface ParkResource {
5 id: string;
6 name: string;
7 location: string;
8 status: "operational" | "maintenance" | "offline";
9 lastUpdated: Date;
10}
11
12// Różne typy zasobów
13interface Dinosaur extends ParkResource {
14 type: "dinosaur";
15 species: string;
16 diet: "carnivore" | "herbivore" | "omnivore";
17 dangerLevel: 1 | 2 | 3 | 4 | 5;
18}
19
20interface Vehicle extends ParkResource {
21 type: "vehicle";
22 model: string;
23 capacity: number;
24 fuelLevel: number;
25}
26
27interface Facility extends ParkResource {
28 type: "facility";
29 purpose: "visitor" | "research" | "maintenance" | "security";
30 size: number; // w m²
31 capacity: number;
32}
33
34interface Employee extends ParkResource {
35 type: "employee";
36 role: string;
37 department: "management" | "scientific" | "technical" | "security" | "visitor";
38 clearanceLevel: 1 | 2 | 3 | 4 | 5;
39}
40
41// Unia wszystkich typów zasobów
42type Resource = Dinosaur | Vehicle | Facility | Employee;
43
44// --- Typy mapowane dla zarządzania zasobami ---
45
46// Typ do przechowywania referencji do zasobów
47type ResourceReferences = {
48 [K in Resource["type"]]: string[]; // Tablica ID dla każdego typu
49};
50
51// Typ do rejestracji procesów zarządzania dla każdego typu zasobu
52type ResourceManagementProcesses = {
53 [K in Resource["type"]]: {
54 create: (data: Omit<Extract<Resource, { type: K }>, "id" | "lastUpdated">) => string;
55 update: (id: string, data: Partial<Extract<Resource, { type: K }>>) => boolean;
56 delete: (id: string) => boolean;
57 audit: (id: string) => AuditReport<Extract<Resource, { type: K }>>;
58 };
59};
60
61// Typ dla raportów audytowych
62type AuditReport<T extends ParkResource> = {
63 resource: T;
64 issues: string[];
65 recommendations: string[];
66 complianceScore: number;
67 auditDate: Date;
68};
69
70// Typ dla uprawnień dostępu do zasobów
71type ResourceAccessPermissions = {
72 [K in Resource["type"]]: {
73 read: string[]; // Role, które mogą czytać
74 write: string[]; // Role, które mogą modyfikować
75 delete: string[]; // Role, które mogą usuwać
76 };
77};
78
79// Typ dla stanów alertów dla zasobów
80type ResourceAlertStatuses = {
81 [K in Resource["type"]]?: {
82 [ID: string]: {
83 level: "info" | "warning" | "critical";
84 message: string;
85 timestamp: Date;
86 acknowledged: boolean;
87 }[];
88 };
89};
90
91// Typ dla statystyk zasobów
92type ResourceStatistics = {
93 [K in Resource["type"]]: {
94 total: number;
95 operational: number;
96 maintenance: number;
97 offline: number;
98 utilizationRate: number; // %
99 };
100};
101
102// Typ dla metadanych formularzy
103type ResourceFormMetadata = {
104 [K in Resource["type"]]: {
105 [Field in keyof Extract<Resource, { type: K }>]: {
106 label: string;
107 required: boolean;
108 type: "text" | "number" | "select" | "date" | "checkbox";
109 options?: string[]; // Dla pól select
110 validation?: RegExp; // Dla walidacji tekstowej
111 min?: number; // Dla pól liczbowych
112 max?: number; // Dla pól liczbowych
113 helpText?: string; // Tekst pomocniczy
114 };
115 };
116};
117
118// --- Implementacja przykładowa ---
119
120// Przykładowa implementacja metadanych formularzy
121const formMetadata: ResourceFormMetadata = {
122 dinosaur: {
123 id: { label: "ID", required: true, type: "text", validation: /^DINO-d{3}$/, helpText: "Format: DINO-XXX" },
124 name: { label: "Nazwa", required: true, type: "text" },
125 type: { label: "Typ", required: true, type: "text" },
126 location: { label: "Lokalizacja", required: true, type: "text" },
127 status: {
128 label: "Status",
129 required: true,
130 type: "select",
131 options: ["operational", "maintenance", "offline"]
132 },
133 lastUpdated: { label: "Ostatnia aktualizacja", required: true, type: "date" },
134 species: { label: "Gatunek", required: true, type: "text" },
135 diet: {
136 label: "Dieta",
137 required: true,
138 type: "select",
139 options: ["carnivore", "herbivore", "omnivore"]
140 },
141 dangerLevel: {
142 label: "Poziom zagrożenia",
143 required: true,
144 type: "select",
145 options: ["1", "2", "3", "4", "5"],
146 helpText: "1 - Łagodny, 5 - Ekstremalnie niebezpieczny"
147 }
148 },
149 vehicle: {
150 id: { label: "ID", required: true, type: "text", validation: /^VEH-d{3}$/, helpText: "Format: VEH-XXX" },
151 name: { label: "Nazwa", required: true, type: "text" },
152 type: { label: "Typ", required: true, type: "text" },
153 location: { label: "Lokalizacja", required: true, type: "text" },
154 status: {
155 label: "Status",
156 required: true,
157 type: "select",
158 options: ["operational", "maintenance", "offline"]
159 },
160 lastUpdated: { label: "Ostatnia aktualizacja", required: true, type: "date" },
161 model: { label: "Model", required: true, type: "text" },
162 capacity: { label: "Pojemność", required: true, type: "number", min: 1, max: 20 },
163 fuelLevel: { label: "Poziom paliwa", required: true, type: "number", min: 0, max: 100, helpText: "W procentach" }
164 },
165 facility: {
166 id: { label: "ID", required: true, type: "text", validation: /^FAC-d{3}$/, helpText: "Format: FAC-XXX" },
167 name: { label: "Nazwa", required: true, type: "text" },
168 type: { label: "Typ", required: true, type: "text" },
169 location: { label: "Lokalizacja", required: true, type: "text" },
170 status: {
171 label: "Status",
172 required: true,
173 type: "select",
174 options: ["operational", "maintenance", "offline"]
175 },
176 lastUpdated: { label: "Ostatnia aktualizacja", required: true, type: "date" },
177 purpose: {
178 label: "Przeznaczenie",
179 required: true,
180 type: "select",
181 options: ["visitor", "research", "maintenance", "security"]
182 },
183 size: { label: "Rozmiar", required: true, type: "number", min: 10, helpText: "W metrach kwadratowych" },
184 capacity: { label: "Pojemność", required: true, type: "number", min: 1 }
185 },
186 employee: {
187 id: { label: "ID", required: true, type: "text", validation: /^EMP-d{3}$/, helpText: "Format: EMP-XXX" },
188 name: { label: "Imię i nazwisko", required: true, type: "text" },
189 type: { label: "Typ", required: true, type: "text" },
190 location: { label: "Stanowisko pracy", required: true, type: "text" },
191 status: {
192 label: "Status",
193 required: true,
194 type: "select",
195 options: ["operational", "maintenance", "offline"]
196 },
197 lastUpdated: { label: "Ostatnia aktualizacja danych", required: true, type: "date" },
198 role: { label: "Stanowisko", required: true, type: "text" },
199 department: {
200 label: "Dział",
201 required: true,
202 type: "select",
203 options: ["management", "scientific", "technical", "security", "visitor"]
204 },
205 clearanceLevel: {
206 label: "Poziom dostępu",
207 required: true,
208 type: "select",
209 options: ["1", "2", "3", "4", "5"],
210 helpText: "1 - Podstawowy, 5 - Pełny dostęp"
211 }
212 }
213};
214
215// Przykładowe uprawnienia dostępu
216const accessPermissions: ResourceAccessPermissions = {
217 dinosaur: {
218 read: ["management", "scientific", "technical", "security", "visitor"],
219 write: ["management", "scientific"],
220 delete: ["management"]
221 },
222 vehicle: {
223 read: ["management", "technical", "security"],
224 write: ["management", "technical"],
225 delete: ["management"]
226 },
227 facility: {
228 read: ["management", "technical", "security"],
229 write: ["management", "technical"],
230 delete: ["management"]
231 },
232 employee: {
233 read: ["management"],
234 write: ["management"],
235 delete: ["management"]
236 }
237};
238
239// Przykładowe statystyki
240const resourceStats: ResourceStatistics = {
241 dinosaur: {
242 total: 15,
243 operational: 12,
244 maintenance: 2,
245 offline: 1,
246 utilizationRate: 80
247 },
248 vehicle: {
249 total: 25,
250 operational: 20,
251 maintenance: 3,
252 offline: 2,
253 utilizationRate: 75
254 },
255 facility: {
256 total: 12,
257 operational: 10,
258 maintenance: 1,
259 offline: 1,
260 utilizationRate: 85
261 },
262 employee: {
263 total: 120,
264 operational: 115,
265 maintenance: 0,
266 offline: 5,
267 utilizationRate: 90
268 }
269};
270
271// Przykładowe alerty
272const alertStatuses: ResourceAlertStatuses = {
273 dinosaur: {
274 "DINO-001": [
275 {
276 level: "warning",
277 message: "Podwyższony poziom agresji w ciągu ostatnich 24 godzin",
278 timestamp: new Date(),
279 acknowledged: false
280 }
281 ],
282 "DINO-005": [
283 {
284 level: "critical",
285 message: "Dinozaur poza wyznaczoną zagrodą!",
286 timestamp: new Date(),
287 acknowledged: true
288 }
289 ]
290 },
291 facility: {
292 "FAC-003": [
293 {
294 level: "warning",
295 message: "Wykryto awarię systemu chłodzenia",
296 timestamp: new Date(),
297 acknowledged: false
298 }
299 ]
300 }
301};
302
303// Generator raportów wykorzystujący typy mapowane
304function generateResourceReport<T extends Resource["type"]>(
305 resourceType: T,
306 detailed: boolean = false
307): {
308 type: T;
309 stats: ResourceStatistics[T];
310 alerts: ResourceAlertStatuses[T] | undefined;
311 resources?: Extract<Resource, { type: T }>[];
312} {
313 // W rzeczywistej implementacji, dane byłyby pobierane z bazy danych
314 return {
315 type: resourceType,
316 stats: resourceStats[resourceType],
317 alerts: alertStatuses[resourceType],
318 // Dodajemy listę zasobów tylko jeśli detailed jest true
319 ...(detailed ? { resources: [] as any } : {})
320 };
321}
322
323// Przykładowe użycie
324const dinosaurReport = generateResourceReport("dinosaur", true);
325console.log(`Raport dla dinozaurów:
326- Łączna liczba: ${dinosaurReport.stats.total}
327- Operacyjne: ${dinosaurReport.stats.operational}
328- W trakcie konserwacji: ${dinosaurReport.stats.maintenance}
329- Offline: ${dinosaurReport.stats.offline}
330- Współczynnik wykorzystania: ${dinosaurReport.stats.utilizationRate}%
331`);
332
333// Wyświetlenie alertów
334if (dinosaurReport.alerts) {
335 console.log("Aktywne alerty:");
336 Object.entries(dinosaurReport.alerts).forEach(([dinoId, alerts]) => {
337 alerts.forEach(alert => {
338 console.log(`- [${alert.level.toUpperCase()}] ${dinoId}: ${alert.message}`);
339 });
340 });
341}TypeScript posiada kilka wbudowanych typów mapowanych, które są bardzo przydatne w codziennej pracy:
- Tworzy typ, w którym wszystkie właściwości Partial<T>
T są opcjonalne1type Partial<T> = { [P in keyof T]?: T[P]; };
- Tworzy typ, w którym wszystkie właściwości Required<T>
T są wymagane1type Required<T> = { [P in keyof T]-?: T[P]; };
- Tworzy typ, w którym wszystkie właściwości Readonly<T>
T są tylko do odczytu1type Readonly<T> = { readonly [P in keyof T]: T[P]; };
- Tworzy typ z kluczami Record<K, T>
K i wartościami typu T1type Record<K extends keyof any, T> = { [P in K]: T; };
- Wybiera podzbiór właściwości Pick<T, K>
K z typu T1type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
- Tworzy typ z wszystkimi właściwościami Omit<T, K>
T oprócz tych wymienionych w K1type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
- Wyciąga z Extract<T, U>
T te typy, które można przypisać do U1type Extract<T, U> = T extends U ? T : never;
- Wyklucza z Exclude<T, U>
T te typy, które można przypisać do U1type Exclude<T, U> = T extends U ? never : T;
- Wyklucza NonNullable<T>
null i undefined z T1type NonNullable<T> = T extends null | undefined ? never : T;
- Wyciąga typy parametrów z typu funkcjiParameters<T>
1type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
- Wyciąga typ zwracany z typu funkcjiReturnType<T>
1type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;Te wbudowane typy mapowane są niezwykle przydatne i pozwalają na bardziej zwięzłe i eleganckie rozwiązania wielu problemów.
Typy mapowane są szczególnie przydatne w następujących scenariuszach:
Transformacja istniejących typów - gdy potrzebujesz stworzyć wariant istniejącego typu (np.
Readonly<T>, Partial<T>)Budowanie formularzy i walidacji - gdy tworzysz dynamiczne formularze na podstawie modelu danych
Konfiguracje i metadane - gdy potrzebujesz stworzyć strukturę konfiguracji lub metadanych dla każdej właściwości typu
Obsługa API - gdy potrzebujesz transformować typy do formatu wymaganego przez API
Generowanie typów pomocniczych - gdy tworzysz typy narzędziowe, które działają na innych typach
TypeScript oferuje jeszcze jeden zaawansowany mechanizm typów — template literal types. Pozwalają one tworzyć nowe typy stringowe poprzez interpolację, podobnie jak template literals w JavaScript, ale na poziomie typów.
1// Podstawowe template literal types
2type DinoEvent = `on${string}`;
3
4// Tylko stringi zaczynające się od "on" są akceptowane
5const event1: DinoEvent = "onClick"; // OK
6const event2: DinoEvent = "onEscape"; // OK
7// const event3: DinoEvent = "escape"; // Błąd!
8
9// Łączenie z union types
10type Species = "TRex" | "Raptor" | "Triceratops";
11type DinoAction = "feed" | "track" | "relocate";
12
13// Generuje: "feedTRex" | "feedRaptor" | ... (wszystkie kombinacje)
14type DinoCommand = `${DinoAction}${Species}`;Template literal types są szczególnie przydatne do tworzenia precyzyjnych typów dla nazw zdarzeń, kluczy konfiguracji czy ścieżek API. Działają one na zasadzie interpolacji stringów, ale na poziomie systemu typów — kompilator TypeScript generuje wszystkie możliwe kombinacje.
Typy mapowane są potężnym mechanizmem TypeScript, który pozwala na systematyczną transformację typów. Podobnie jak w Parku Jurajskim, gdzie te same dane o dinozaurach mogą być przetwarzane na różne sposoby w zależności od potrzeb, typy mapowane pozwalają nam tworzyć nowe typy na podstawie istniejących, dostosowując je do konkretnych wymagań.
Dzięki typom mapowanym możemy tworzyć bardziej elastyczne, typowo bezpieczne API, formularze, walidacje i wiele innych struktur danych. Mechanizm ten jest szczególnie potężny w połączeniu z innymi zaawansowanymi funkcjami TypeScript, takimi jak typy warunkowe czy inferencja typów.
W miarę jak będziesz zdobywać więcej doświadczenia w TypeScript, typy mapowane staną się nieodzownym narzędziem w twoim arsenale, pozwalającym na tworzenie bardziej ekspresyjnych, bezpiecznych i eleganckich rozwiązań.