Kurs JavaScript i TypeScript · Moduł 7: Zaawansowany TypeScript
Projekt końcowy: Zaawansowany system typów dla Parku Jurajskiego
W tej lekcji4
"Nadszedł czas na podsumowanie," ogłasza Dr. Henry Wu, główny genetyk InGen, stojąc przed ogromnym holoekranem laboratorium. "Przez ostatnie tygodnie opanowałeś zaawansowane mechanizmy TypeScript - od modyfikatorów dostępu, przez generyki, po typy warunkowe i mapowane. Teraz zbudujesz kompletny system zarządzania Parkiem Jurajskim, który połączy wszystkie te elementy w jedną, spójną całość."
W tym projekcie stworzysz zaawansowany system typów dla Parku Jurajskiego. Wykorzystasz klasy abstrakcyjne, interfejsy, generyki, typy warunkowe, typy mapowane, utility types i dekoratory - wszystko to, czego nauczyłeś się w tym module. System będzie obsługiwał rejestrację dinozaurów, zarządzanie zagrodami, monitorowanie stanu zdrowia i generowanie raportów bezpieczeństwa.
Cel projektu
Zbudujesz kompletny, w pełni typowany system JurassicParkManager, który:
- Wykorzystuje klasy abstrakcyjne jako bazę dla różnych kategorii dinozaurów
- Implementuje interfejsy definiujące kontrakty dla podsystemów parku
- Używa generyków do tworzenia elastycznych struktur danych
- Stosuje typy warunkowe do dynamicznego określania zachowań
- Wykorzystuje typy mapowane do transformacji danych
- Korzysta z utility types do precyzyjnego modelowania
Wymagania funkcjonalne
1. Hierarchia klas dinozaurów z klasami abstrakcyjnymi i interfejsami
Zdefiniuj abstrakcyjną klasę bazową Dinosaur z właściwościami chronionymi i abstrakcyjnymi metodami. Następnie stwórz interfejsy opisujące różne zdolności dinozaurów i zaimplementuj je w klasach potomnych.
// Interfejsy zdolności
interface IHerbivore {
graze(area: string): string;
preferredPlants: string[];
}
interface ICarnivore {
hunt(target: string): string;
attackPower: number;
}
interface IFlying {
fly(altitude: number): string;
maxAltitude: number;
}
interface ISwimming {
swim(depth: number): string;
maxDepth: number;
}
// Klasa abstrakcyjna z modyfikatorami dostępu
abstract class Dinosaur {
private static nextId: number = 1;
protected readonly id: number;
public readonly species: string;
protected health: number;
private enclosureId: string | null = null;
constructor(species: string, health: number = 100) {
this.id = Dinosaur.nextId++;
this.species = species;
this.health = health;
}
// Metoda abstrakcyjna - każdy podtyp definiuje swój poziom zagrożenia
abstract get dangerLevel(): 'low' | 'medium' | 'high' | 'critical';
// Metoda abstrakcyjna - opis zachowania
abstract describeBehavior(): string;
// Statyczna metoda fabrykująca
static getNextId(): number {
return Dinosaur.nextId;
}
// Publiczne API
public getStatus(): string {
return `[${this.id}] ${this.species} - Zdrowie: ${this.health}%`;
}
public assignEnclosure(enclosureId: string): void {
this.enclosureId = enclosureId;
}
protected heal(amount: number): void {
this.health = Math.min(100, this.health + amount);
}
}
// Klasa potomna implementująca interfejsy
class TyrannosaurusRex extends Dinosaur implements ICarnivore {
public attackPower: number = 95;
constructor() {
super('Tyrannosaurus Rex', 100);
}
get dangerLevel(): 'critical' {
return 'critical';
}
describeBehavior(): string {
return 'Drapieżnik szczytowy - wymaga maksymalnych zabezpieczeń';
}
hunt(target: string): string {
return `T-Rex poluje na ${target} z siłą ataku ${this.attackPower}`;
}
}
class Pteranodon extends Dinosaur implements ICarnivore, IFlying {
public attackPower: number = 40;
public maxAltitude: number = 500;
constructor() {
super('Pteranodon', 100);
}
get dangerLevel(): 'high' {
return 'high';
}
describeBehavior(): string {
return 'Latający drapieżnik - wymaga zadaszonej zagrody';
}
hunt(target: string): string {
return `Pteranodon nurkuje na ${target} z powietrza`;
}
fly(altitude: number): string {
if (altitude > this.maxAltitude) {
return 'Wysokość przekracza maksymalny pułap!';
}
return `Pteranodon leci na wysokości ${altitude}m`;
}
}2. Generyczne struktury danych z ograniczeniami
Stwórz generyczne klasy i funkcje z odpowiednimi constraintami, aby zapewnić bezpieczeństwo typów w całym systemie.
// Generyczna klasa rejestru z ograniczeniem
class ParkRegistry<T extends Dinosaur> {
private entries: Map<number, T> = new Map();
add(item: T): void {
this.entries.set(item['id'], item);
}
get(id: number): T | undefined {
return this.entries.get(id);
}
getAll(): T[] {
return Array.from(this.entries.values());
}
// Generyczna metoda z dodatkowym ograniczeniem
filter<K extends keyof T>(key: K, value: T[K]): T[] {
return this.getAll().filter(item => item[key] === value);
}
// Metoda z wieloma typami generycznymi
map<U>(transform: (item: T) => U): U[] {
return this.getAll().map(transform);
}
}
// Generyczna funkcja z wieloma ograniczeniami
function transferDinosaur<
T extends Dinosaur,
S extends ParkRegistry<T>,
D extends ParkRegistry<T>
>(source: S, destination: D, dinoId: number): boolean {
const dino = source.get(dinoId);
if (!dino) return false;
destination.add(dino);
return true;
}
// Generyczny interfejs dla systemów monitoringu
interface IMonitoringSystem<T extends Dinosaur> {
monitor(subject: T): MonitoringReport<T>;
getAlerts(): Alert<T>[];
}
// Generyczny typ raportu
interface MonitoringReport<T extends Dinosaur> {
subject: T;
timestamp: Date;
status: 'normal' | 'warning' | 'danger';
details: string;
}
interface Alert<T extends Dinosaur> {
dinosaur: T;
level: 'info' | 'warning' | 'critical';
message: string;
}3. Typy warunkowe i mapowane do dynamicznego modelowania
Wykorzystaj conditional types i mapped types, aby automatycznie dostosowywać zachowanie systemu w zależności od typu dinozaura i poziomu zagrożenia.
// Typ warunkowy - określa wymagania zagrody na podstawie poziomu zagrożenia
type DangerLevel = 'low' | 'medium' | 'high' | 'critical';
type EnclosureRequirements<D extends DangerLevel> =
D extends 'critical' ? {
fenceVoltage: number;
guardCount: number;
cameraCount: number;
backupPower: boolean;
emergencyProtocol: string;
} :
D extends 'high' ? {
fenceVoltage: number;
guardCount: number;
cameraCount: number;
backupPower: boolean;
} :
D extends 'medium' ? {
fenceVoltage: number;
cameraCount: number;
} :
{
fenceType: string;
};
// Użycie typu warunkowego
const trexEnclosure: EnclosureRequirements<'critical'> = {
fenceVoltage: 10000,
guardCount: 4,
cameraCount: 12,
backupPower: true,
emergencyProtocol: 'Ewakuacja sektora i wysłanie zespołu interwencyjnego'
};
// Typy mapowane - generowanie wersji readonly i opcjonalnych
type DinosaurData = {
species: string;
health: number;
location: string;
lastFed: Date;
dangerLevel: DangerLevel;
};
// Mapped type: wersja do wyświetlania (readonly)
type ReadonlyDinosaurData = {
readonly [K in keyof DinosaurData]: DinosaurData[K];
};
// Mapped type: wersja do aktualizacji (wszystko opcjonalne)
type DinosaurUpdate = {
[K in keyof DinosaurData]?: DinosaurData[K];
};
// Mapped type: wyciągnij tylko pola typu string
type StringFieldsOnly<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type DinosaurStringFields = StringFieldsOnly<DinosaurData>;
// Rezultat: { species: string; location: string; }
// Zaawansowany mapped type z warunkową transformacją
type DinosaurDisplay<T> = {
[K in keyof T]: T[K] extends Date
? string // Daty zamień na stringi
: T[K] extends number
? `${number}%` // Liczby z procentem
: T[K]; // Reszta bez zmian
};
// Conditional type z infer
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type DinosaurFactory = (species: string) => Dinosaur;
type CreatedDino = ExtractReturnType<DinosaurFactory>; // Dinosaur
// Dystrybucyjny conditional type
type ExcludeDangerLevels<T, U> = T extends U ? never : T;
type SafeLevels = ExcludeDangerLevels<DangerLevel, 'high' | 'critical'>;
// Rezultat: 'low' | 'medium'4. Utility Types i zaawansowane wzorce typów
Zastosuj wbudowane utility types oraz stwórz własne, aby precyzyjnie modelować operacje na danych dinozaurów.
// Wbudowane utility types
type NewDinosaur = Omit<DinosaurData, 'lastFed'> & {
registrationDate: Date;
};
type DinosaurPreview = Pick<DinosaurData, 'species' | 'dangerLevel'>;
type DinosaurRecord = Record<string, DinosaurData>;
// Wymaganie pól, które wcześniej były opcjonalne
type RequiredDinosaurUpdate = Required<DinosaurUpdate>;
// Własne utility types
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K];
};
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? T[K] extends Function
? T[K]
: DeepPartial<T[K]>
: T[K];
};
// Typ wykluczający null i undefined z każdego pola
type NonNullableFields<T> = {
[K in keyof T]: NonNullable<T[K]>;
};
// System konfiguracji zagrody z pełnym typowaniem
interface EnclosureConfig {
dimensions: {
width: number;
height: number;
depth: number;
};
security: {
fenceType: 'electric' | 'reinforced' | 'laser';
voltage?: number;
guardSchedule: string[];
};
environment: {
temperature: number;
humidity: number;
terrain: 'forest' | 'plains' | 'water' | 'mixed';
};
}
// Deep partial pozwala na aktualizację dowolnie zagnieżdżonego pola
type EnclosureUpdate = DeepPartial<EnclosureConfig>;
const partialUpdate: EnclosureUpdate = {
security: {
voltage: 15000 // Aktualizuj tylko napięcie
}
};
// Deep readonly dla konfiguracji, która nie powinna być zmieniana
type FrozenConfig = DeepReadonly<EnclosureConfig>;Wskazówki implementacyjne
Zacznij od zdefiniowania interfejsów i typów bazowych - to fundament całego systemu. Następnie stwórz klasę abstrakcyjną Dinosaur i kilka klas potomnych implementujących różne interfejsy zdolności. Potem zbuduj generyczny ParkRegistry i system monitoringu. Na końcu dodaj typy warunkowe i mapowane, które dynamicznie dostosowują zachowanie systemu.
Pamiętaj o następującej strukturze plików:
// 1. Typy i interfejsy bazowe
// 2. Klasa abstrakcyjna Dinosaur
// 3. Klasy potomne (TRex, Velociraptor, Triceratops, Pteranodon...)
// 4. Generyczny ParkRegistry<T>
// 5. System monitoringu z typami warunkowymi
// 6. Utility types i transformacje
// 7. Główna klasa JurassicParkManager łącząca wszystkoPrzykład inicjalizacji systemu:
// Tworzenie rejestru
const carnivoreRegistry = new ParkRegistry<TyrannosaurusRex>();
const herbivoreRegistry = new ParkRegistry<Triceratops>();
// Dodawanie dinozaurów
const rex = new TyrannosaurusRex();
carnivoreRegistry.add(rex);
// Konfiguracja zagrody z typami warunkowymi
const criticalEnclosure: EnclosureRequirements<'critical'> = {
fenceVoltage: 10000,
guardCount: 4,
cameraCount: 12,
backupPower: true,
emergencyProtocol: 'Kod Czerwony - pełna ewakuacja'
};
// Generowanie raportu
const reports = carnivoreRegistry.map(dino => ({
species: dino.species,
status: dino.getStatus(),
danger: dino.dangerLevel
}));
console.log('=== Raport Parku Jurajskiego ===');
reports.forEach(r => console.log(`${r.species}: ${r.status} [Zagrożenie: ${r.danger}]`));Powodzenia!
"Pamiętaj," mówi Dr. Wu, poprawiając okulary, "system typów TypeScript to Twoje DNA - fundament, na którym budujesz cały ekosystem. Im lepiej zaprojektujesz typy, tym stabilniejszy będzie Twój Park. Generyki dają elastyczność, typy warunkowe - inteligencję, a typy mapowane - zdolność do transformacji. Połącz je wszystkie i stwórz system, który przetrwa próbę czasu... i próbę uciekających dinozaurów."
Czas zastosować wszystko, czego się nauczyłeś. Otwórz edytor i zbuduj swój zaawansowany system typów!
Kod do tej lekcji: index.ts
// Projekt koncowy: Zaawansowany system typow dla Parku Jurajskiego
// Polacz: generyki, typy warunkowe, typy mapowane, utility types
// === 1. INTERFEJSY Z GENERYKAMI ===
// TODO: Stworz generyczny interfejs Repository<T>
interface Repository<T extends { id: string }> {
items: T[];
add(item: T): void;
findById(id: string): T | undefined;
// TODO: Dodaj metody: remove, update, filter
}
// === 2. KLASA ABSTRAKCYJNA Z GENERYKAMI ===
// TODO: Stworz abstrakcyjna klase Dinosaur z modyfikatorami dostepu
abstract class Dinosaur {
protected readonly id: string;
protected name: string;
constructor(id: string, name: string) {
this.id = id;
this.name = name;
}
// TODO: Dodaj abstrakcyjne metody: getDangerLevel(), getSound()
abstract getDangerLevel(): number;
abstract getSound(): string;
}
// TODO: Stworz klasy Carnivore i Herbivore rozszerzajace Dinosaur
// === 3. TYPY WARUNKOWE ===
// TODO: Stworz typ warunkowy DinoResponse<T>
// Jezeli T extends "carnivore" -> zwraca CarnivoreData
// Jezeli T extends "herbivore" -> zwraca HerbivoreData
type DinoCategory = "carnivore" | "herbivore";
interface CarnivoreData {
attackPower: number;
huntingStyle: string;
}
interface HerbivoreData {
grazingArea: number;
preferredPlants: string[];
}
// TODO: Zdefiniuj typ warunkowy DinoResponse<T extends DinoCategory>
// === 4. TYPY MAPOWANE ===
// TODO: Stworz typ ReadonlyDino<T> (deep readonly)
// TODO: Stworz typ Optional<T> (wszystkie pola opcjonalne)
// TODO: Stworz typ DinoFormFields<T> (wartosci zamienione na string)
// === 5. PARK MANAGER ===
// TODO: Stworz klase JurassicParkManager uzywajaca generykow
// z metodami do rejestracji, wyszukiwania i raportowania
console.log("Zaawansowany system typow uruchomiony!");Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?