Dr. Henry Wu przegląda bazę danych genetycznych Parku Jurajskiego. "Mamy interfejs Dinosaur z 15 właściwościami," mówi, "ale do formularza aktualizacji potrzebuję tylko 5, a do podglądu - zaledwie 3. Czy muszę tworzyć osobny interfejs dla każdego przypadku?" Na szczęście TypeScript oferuje typy narzędziowe (Utility Types) - gotowe transformacje, które tworzą nowe typy na podstawie istniejących.
Typy narzędziowe (Utility Types) to wbudowane w TypeScript generyczne typy, które pozwalają transformować istniejące typy w nowe. Zamiast pisać osobne interfejsy dla każdego wariantu, możesz użyć jednego bazowego typu i tworzyć z niego pochodne.
Partial<T> sprawia, że wszystkie właściwości typu T stają się opcjonalne. Idealny do aktualizacji, gdzie nie chcemy wymagać podania wszystkich pól:1interface Dinosaur {
2 name: string;
3 species: string;
4 dangerLevel: number;
5 enclosureId: string;
6 dietType: 'herbivore' | 'carnivore' | 'omnivore';
7}
8
9// Partial<Dinosaur> = wszystkie pola opcjonalne
10type DinosaurUpdate = Partial<Dinosaur>;
11
12// Teraz możemy przekazać tylko zmienione pola
13function updateDinosaur(id: string, changes: DinosaurUpdate): void {
14 console.log(`Aktualizuję dinozaura ${id}:`, changes);
15}
16
17// Poprawne - przekazujemy tylko to, co zmieniamy
18updateDinosaur('DINO-001', { dangerLevel: 9 });
19updateDinosaur('DINO-002', { name: 'Blue', enclosureId: 'E-7' });Required<T> jest odwrotnością Partial - sprawia, że wszystkie właściwości (nawet opcjonalne) stają się wymagane:1interface DinosaurConfig {
2 name: string;
3 trackingChip?: boolean; // opcjonalne
4 feedingSchedule?: string; // opcjonalne
5}
6
7// Required<DinosaurConfig> = wszystko wymagane
8type CompleteDinosaurConfig = Required<DinosaurConfig>;
9
10// Teraz trackingChip i feedingSchedule też są wymagane
11const config: CompleteDinosaurConfig = {
12 name: 'Rex',
13 trackingChip: true, // musi być podane
14 feedingSchedule: '08:00' // musi być podane
15};Pick<T, K> tworzy nowy typ zawierający tylko wybrane właściwości z T:1// Tylko name i species z pełnego interfejsu Dinosaur
2type DinosaurPreview = Pick<Dinosaur, 'name' | 'species'>;
3
4// DinosaurPreview = { name: string; species: string; }
5
6const preview: DinosaurPreview = {
7 name: 'Blue',
8 species: 'Velociraptor'
9};
10
11// Przydatne np. do listy wyświetlanej na ekranie
12function renderDinosaurList(dinosaurs: DinosaurPreview[]): void {
13 dinosaurs.forEach(dino => {
14 console.log(`${dino.name} (${dino.species})`);
15 });
16}Omit<T, K> jest odwrotnością Pick - tworzy nowy typ bez wybranych właściwości:1// Dinosaur bez enclosureId (np. dla nowo odkrytego gatunku)
2type DinosaurWithoutEnclosure = Omit<Dinosaur, 'enclosureId'>;
3
4const newDiscovery: DinosaurWithoutEnclosure = {
5 name: 'Nowy gatunek',
6 species: 'Unknownus rex',
7 dangerLevel: 5,
8 dietType: 'carnivore'
9 // nie musimy podawać enclosureId
10};
11
12// Omit wielu pól naraz
13type DinosaurBasicInfo = Omit<Dinosaur, 'enclosureId' | 'dangerLevel'>;Record<K, V> tworzy typ obiektu, gdzie klucze mają typ K, a wartości typ V:1// Słownik: nazwa strefy -> lista dinozaurów
2type ZoneMap = Record<string, Dinosaur[]>;
3
4const parkZones: ZoneMap = {
5 'Zone-A': [{ name: 'Rex', species: 'T-Rex', dangerLevel: 10, enclosureId: 'E-1', dietType: 'carnivore' }],
6 'Zone-B': [{ name: 'Trike', species: 'Triceratops', dangerLevel: 3, enclosureId: 'E-2', dietType: 'herbivore' }]
7};
8
9// Record z ograniczonymi kluczami (union type)
10type DietCount = Record<'herbivore' | 'carnivore' | 'omnivore', number>;
11
12const census: DietCount = {
13 herbivore: 45,
14 carnivore: 12,
15 omnivore: 8
16};Readonly<T> sprawia, że wszystkie właściwości stają się tylko do odczytu:1type FrozenDinosaur = Readonly<Dinosaur>;
2
3const frozenDNA: FrozenDinosaur = {
4 name: 'Amber Specimen',
5 species: 'Unknown',
6 dangerLevel: 0,
7 enclosureId: 'LAB-1',
8 dietType: 'herbivore'
9};
10
11// frozenDNA.name = 'Changed'; // Błąd! Cannot assign to 'name' because it is a read-only propertyPrawdziwa moc pojawia się przy łączeniu kilku utility types:
1// Formularz tworzenia dinozaura: wszystkie pola wymagane OPRÓCZ enclosureId
2type CreateDinosaurForm = Omit<Dinosaur, 'enclosureId'> & {
3 enclosureId?: string; // opcjonalne przy tworzeniu
4};
5
6// Formularz edycji: tylko wybrane pola, wszystkie opcjonalne
7type EditDinosaurForm = Partial<Pick<Dinosaur, 'name' | 'dangerLevel' | 'enclosureId'>>;
8
9// Niezmienialny podgląd z wybranymi polami
10type DinosaurReadonlyPreview = Readonly<Pick<Dinosaur, 'name' | 'species' | 'dangerLevel'>>;
11
12const readonlyPreview: DinosaurReadonlyPreview = {
13 name: 'Blue',
14 species: 'Velociraptor',
15 dangerLevel: 8
16};
17// readonlyPreview.dangerLevel = 10; // Błąd!Typy narzędziowe TypeScript pozwalają tworzyć warianty istniejących typów bez duplikacji kodu:
| Typ | Działanie | |-----|-----------| |
Partial<T> | Wszystkie pola opcjonalne |
| Required<T> | Wszystkie pola wymagane |
| Pick<T, K> | Wybierz konkretne pola |
| Omit<T, K> | Pomiń konkretne pola |
| Record<K, V> | Słownik typowany |
| Readonly<T> | Wszystkie pola tylko do odczytu |Jak mówi Dr. Wu: "Nie twórz 10 osobnych interfejsów, gdy jeden bazowy typ i utility types załatwią sprawę elegancko i bezpiecznie!"