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

Typy narzędziowe TypeScript (Utility Types)

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.

Czym są typy narzędziowe?

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> - wszystkie właściwości opcjonalne

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> - wszystkie właściwości wymagane

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> - wybierz właściwości

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> - pomiń właściwości

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> - słownik typowany

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> - niezmienialny typ

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 property

Łączenie typów narzędziowych

Prawdziwa 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!

Podsumowanie

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

Przejdź do CodeWorlds