Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Mapped types (Typy mapowane)

"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.

Czym są typy mapowane?

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łowy
  • K in keyof T
    iteruje przez wszystkie klucze typu
    T
  • TransformedType
    to nowy typ, który zostanie przypisany do każdej właściwości

Prosty przykład: Readonly

Jednym 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 property

Zmiana modyfikatorów właściwości

Typy 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ść).

Dodawanie modyfikatorów

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};

Usuwanie modyfikatorów

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// };

Transformacja typów właściwości

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};

Warunkowe typy mapowane

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};

Praktyczne zastosowania typów mapowanych

Tworzenie typu konfiguracji na podstawie istniejącego typu

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};

Tworzenie formularzy na podstawie modelu danych

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};

Śledzenie zmian w obiektach

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 }

Zaawansowane techniki typów mapowanych

Filtrowanie kluczy na podstawie typów wartości

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};

Rekursywne typy mapowane

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; // Error

Zaawansowany przykład: System zarządzania zasobami parku

Poniż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}

Wbudowane typy mapowane w TypeScript

TypeScript posiada kilka wbudowanych typów mapowanych, które są bardzo przydatne w codziennej pracy:

  1. Partial<T>
    - Tworzy typ, w którym wszystkie właściwości
    T
    są opcjonalne

    1type Partial<T> = { [P in keyof T]?: T[P]; };
  2. Required<T>
    - Tworzy typ, w którym wszystkie właściwości
    T
    są wymagane

    1type Required<T> = { [P in keyof T]-?: T[P]; };
  3. Readonly<T>
    - Tworzy typ, w którym wszystkie właściwości
    T
    są tylko do odczytu

    1type Readonly<T> = { readonly [P in keyof T]: T[P]; };
  4. Record<K, T>
    - Tworzy typ z kluczami
    K
    i wartościami typu
    T

    1type Record<K extends keyof any, T> = { [P in K]: T; };
  5. Pick<T, K>
    - Wybiera podzbiór właściwości
    K
    z typu
    T

    1type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
  6. Omit<T, K>
    - Tworzy typ z wszystkimi właściwościami
    T
    oprócz tych wymienionych w
    K

    1type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  7. Extract<T, U>
    - Wyciąga z
    T
    te typy, które można przypisać do
    U

    1type Extract<T, U> = T extends U ? T : never;
  8. Exclude<T, U>
    - Wyklucza z
    T
    te typy, które można przypisać do
    U

    1type Exclude<T, U> = T extends U ? never : T;
  9. NonNullable<T>
    - Wyklucza
    null
    i
    undefined
    z
    T

    1type NonNullable<T> = T extends null | undefined ? never : T;
  10. Parameters<T>
    - Wyciąga typy parametrów z typu funkcji

    1type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
  11. ReturnType<T>
    - Wyciąga typ zwracany z typu funkcji

    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.

Kiedy używać typów mapowanych?

Typy mapowane są szczególnie przydatne w następujących scenariuszach:

  1. Transformacja istniejących typów - gdy potrzebujesz stworzyć wariant istniejącego typu (np.

    Readonly<T>
    ,
    Partial<T>
    )

  2. Budowanie formularzy i walidacji - gdy tworzysz dynamiczne formularze na podstawie modelu danych

  3. Konfiguracje i metadane - gdy potrzebujesz stworzyć strukturę konfiguracji lub metadanych dla każdej właściwości typu

  4. Obsługa API - gdy potrzebujesz transformować typy do formatu wymaganego przez API

  5. Generowanie typów pomocniczych - gdy tworzysz typy narzędziowe, które działają na innych typach

Template literal types

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.

Podsumowanie

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ń.

Przejdź do CodeWorlds