"W Parku Jurajskim mamy dziesiątki systemów - monitoring, genetykę, bezpieczeństwo, logistykę," wyjaśnia Dennis Nedry, główny programista parku. "Każdy system ma setki zmiennych, typów, funkcji. Bez porządnej organizacji nazwy zaczęłyby się powtarzać, a chaos stałby się nie do opanowania. Potrzebujemy sposobu na oddzielenie każdego systemu, a jednocześnie pozwolenie im na współpracę."
W TypeScript istnieje kilka mechanizmów organizacji kodu na poziomie typów: namespaces (przestrzenie nazw), declaration merging (łączenie deklaracji) i ambient types (deklaracje otoczenia). Te techniki pozwalają na tworzenie dobrze zorganizowanych, rozszerzalnych systemów typów.
Namespace to sposób grupowania powiązanych typów, interfejsów, funkcji i zmiennych pod wspólną nazwą. Zapobiega kolizjom nazw i logicznie organizuje kod.
1// Namespace dla systemu bezpieczeństwa
2namespace Security {
3 export interface Camera {
4 id: string;
5 zone: string;
6 isActive: boolean;
7 }
8
9 export interface Guard {
10 id: string;
11 name: string;
12 assignedZone: string;
13 }
14
15 export type AlertLevel = "green" | "yellow" | "red" | "black";
16
17 export function getAlertDescription(level: AlertLevel): string {
18 switch (level) {
19 case "green": return "Wszystko w normie";
20 case "yellow": return "Podwyższona czujność";
21 case "red": return "Zagrożenie - ewakuacja gości";
22 case "black": return "Krytyczne - pełna ewakuacja";
23 }
24 }
25}
26
27// Namespace dla systemu genetycznego
28namespace Genetics {
29 export interface Sample {
30 id: string;
31 species: string;
32 purity: number;
33 }
34
35 // Ten "Sample" nie koliduje z Security.Camera
36 // bo jest w innym namespace
37 export function analyzeSample(sample: Sample): string {
38 return `Analiza próbki ${sample.id}: czystość ${sample.purity}%`;
39 }
40}
41
42// Użycie - odwoływanie się przez nazwę namespace
43const camera: Security.Camera = { id: "CAM-01", zone: "Strefa T-Rex", isActive: true };
44const sample: Genetics.Sample = { id: "DNA-42", species: "Velociraptor", purity: 87 };
45
46console.log(Security.getAlertDescription("red"));
47console.log(Genetics.analyzeSample(sample));Namespaces mogą być zagnieżdżone, tworząc hierarchiczną strukturę:
1namespace JurassicPark {
2 export namespace Monitoring {
3 export interface SensorReading {
4 sensorId: string;
5 temperature: number;
6 humidity: number;
7 timestamp: Date;
8 }
9
10 export function isReadingNormal(reading: SensorReading): boolean {
11 return reading.temperature >= 20 && reading.temperature <= 35;
12 }
13 }
14
15 export namespace Feeding {
16 export interface Schedule {
17 dinosaurId: string;
18 food: string;
19 amountKg: number;
20 time: string;
21 }
22
23 export function createSchedule(dinoId: string, food: string, kg: number, time: string): Schedule {
24 return { dinosaurId: dinoId, food, amountKg: kg, time };
25 }
26 }
27}
28
29// Użycie
30const reading: JurassicPark.Monitoring.SensorReading = {
31 sensorId: "TEMP-A3",
32 temperature: 28,
33 humidity: 65,
34 timestamp: new Date()
35};
36
37const schedule = JurassicPark.Feeding.createSchedule("DINO-001", "Koza", 50, "08:00");
38console.log("Temperatura normalna:", JurassicPark.Monitoring.isReadingNormal(reading));
39console.log("Harmonogram:", JSON.stringify(schedule));Declaration merging to mechanizm TypeScript, w którym kompilator łączy dwie lub więcej osobnych deklaracji o tej samej nazwie w jedną definicję. To szczególnie przydatne do rozszerzania istniejących typów.
Najczęstszym typem declaration merging jest łączenie interfejsów. Gdy zadeklarujesz ten sam interfejs wielokrotnie, TypeScript połączy je w jeden:
1// Bazowy interfejs dinozaura
2interface ParkDinosaur {
3 id: string;
4 name: string;
5 species: string;
6}
7
8// Rozszerzenie tego samego interfejsu - automatycznie się łączą
9interface ParkDinosaur {
10 weight: number;
11 diet: "carnivore" | "herbivore" | "omnivore";
12}
13
14// Kolejne rozszerzenie
15interface ParkDinosaur {
16 enclosureId: string;
17 dangerLevel: number;
18}
19
20// Wynikowy typ ParkDinosaur ma WSZYSTKIE pola ze wszystkich deklaracji
21const rex: ParkDinosaur = {
22 id: "D-001",
23 name: "Rexy",
24 species: "Tyrannosaurus Rex",
25 weight: 8000,
26 diet: "carnivore",
27 enclosureId: "ENC-A1",
28 dangerLevel: 10
29};
30
31console.log(`${rex.name}: ${rex.weight}kg, zagrożenie: ${rex.dangerLevel}/10`);Namespaces o tej samej nazwie też się łączą:
1namespace ParkSystems {
2 export function getVersion(): string {
3 return "1.0.0";
4 }
5}
6
7namespace ParkSystems {
8 export function getStatus(): string {
9 return "Operational";
10 }
11}
12
13// Oba eksporty są dostępne
14console.log(ParkSystems.getVersion());
15console.log(ParkSystems.getStatus());Namespace może rozszerzać klasę o statyczne właściwości:
1class Enclosure {
2 name: string;
3 capacity: number;
4
5 constructor(name: string, capacity: number) {
6 this.name = name;
7 this.capacity = capacity;
8 }
9}
10
11// Namespace o tej samej nazwie dodaje statyczne elementy
12namespace Enclosure {
13 export interface Config {
14 fenceVoltage: number;
15 cameraCount: number;
16 guardCount: number;
17 }
18
19 export const DEFAULT_CONFIG: Config = {
20 fenceVoltage: 5000,
21 cameraCount: 4,
22 guardCount: 2
23 };
24
25 export function createHighSecurity(name: string, capacity: number): Enclosure {
26 const enc = new Enclosure(name, capacity);
27 // Użyj domyślnej konfiguracji...
28 return enc;
29 }
30}
31
32// Użycie klasy i namespace razem
33const enc = new Enclosure("Strefa Raptorów", 6);
34const config = Enclosure.DEFAULT_CONFIG;
35const secureEnc = Enclosure.createHighSecurity("Strefa T-Rex", 2);
36
37console.log(`Zagroda: ${enc.name}, Domyślne napięcie: ${config.fenceVoltage}V`);Słowo kluczowe
declare informuje TypeScript, że dana zmienna, funkcja lub klasa istnieje w runtime, ale jest zdefiniowana gdzie indziej (np. w globalnym skrypcie, bibliotece zewnętrznej). Kompilator nie generuje kodu dla declare - tylko sprawdza typy.1// Deklaracja zmiennej globalnej
2declare const PARK_VERSION: string;
3declare const MAX_DINOSAURS: number;
4
5// Deklaracja funkcji z zewnętrznej biblioteki
6declare function trackDinosaur(id: string): { x: number; y: number };
7declare function sendAlert(message: string, zone: string): void;
8
9// Deklaracja modułu zewnętrznego
10declare module "jurassic-utils" {
11 export function calculateFeedingAmount(weight: number, species: string): number;
12 export function getEnclosureRecommendation(dangerLevel: number): string;
13}
14
15// Deklaracja klasy z zewnętrznego systemu
16declare class ExternalTrackingSystem {
17 connect(apiKey: string): Promise<void>;
18 getPosition(dinoId: string): { lat: number; lng: number };
19 disconnect(): void;
20}Pliki z rozszerzeniem
.d.ts zawierają WYŁĄCZNIE deklaracje typów - żadnego kodu wykonywalnego. Służą do opisywania typów bibliotek JavaScript, które nie mają wbudowanych typów.1// Przykład pliku jurassic-park.d.ts
2// (tylko deklaracje, bez implementacji)
3
4declare namespace JurassicParkAPI {
5 interface DinosaurResponse {
6 id: string;
7 name: string;
8 species: string;
9 location: { lat: number; lng: number };
10 }
11
12 interface EnclosureResponse {
13 id: string;
14 name: string;
15 status: "normal" | "lockdown" | "breach";
16 dinosaurIds: string[];
17 }
18
19 function fetchDinosaur(id: string): Promise<DinosaurResponse>;
20 function fetchEnclosure(id: string): Promise<EnclosureResponse>;
21 function getAllDinosaurs(): Promise<DinosaurResponse[]>;
22}Możesz dodawać typy do istniejących modułów za pomocą
declare module:1// Dodawanie pól do interfejsu Express Request (częsty przypadek)
2// Plik: types/express.d.ts
3declare module "express" {
4 interface Request {
5 parkUser?: {
6 id: string;
7 role: "visitor" | "staff" | "admin";
8 clearanceLevel: number;
9 };
10 }
11}
12
13// Rozszerzanie globalnych typów
14declare global {
15 interface Array<T> {
16 // Dodajemy metodę do tablicy
17 findDinosaur(predicate: (item: T) => boolean): T | undefined;
18 }
19
20 interface Window {
21 parkConfig: {
22 apiUrl: string;
23 version: string;
24 debugMode: boolean;
25 };
26 }
27}
28
29// Ważne: plik musi mieć przynajmniej jeden import/export,
30// by TypeScript traktował go jako moduł
31export {};Poniżej przykład łączący namespaces, declaration merging i ambient declarations w jednym systemie:
1// Namespace z typami bazowymi
2namespace Park {
3 export interface Entity {
4 id: string;
5 createdAt: Date;
6 }
7
8 export type Priority = "low" | "medium" | "high" | "critical";
9}
10
11// Declaration merging - rozszerzamy Park o nowe typy
12namespace Park {
13 export interface Dinosaur extends Entity {
14 name: string;
15 species: string;
16 dangerLevel: number;
17 }
18
19 export interface Enclosure extends Entity {
20 name: string;
21 capacity: number;
22 residents: string[];
23 }
24}
25
26// Kolejne rozszerzenie - funkcje pomocnicze
27namespace Park {
28 export function isDangerous(dino: Dinosaur): boolean {
29 return dino.dangerLevel >= 7;
30 }
31
32 export function getEnclosureStatus(enc: Enclosure): string {
33 const ratio = enc.residents.length / enc.capacity;
34 if (ratio >= 1) return "PEŁNA";
35 if (ratio >= 0.75) return "Prawie pełna";
36 return "OK";
37 }
38}
39
40// Użycie - wszystkie rozszerzenia działają razem
41const trex: Park.Dinosaur = {
42 id: "D-001",
43 createdAt: new Date(),
44 name: "Rexy",
45 species: "T-Rex",
46 dangerLevel: 10
47};
48
49const enclosure: Park.Enclosure = {
50 id: "E-001",
51 createdAt: new Date(),
52 name: "Strefa T-Rex",
53 capacity: 2,
54 residents: ["D-001"]
55};
56
57console.log(`${trex.name} niebezpieczny: ${Park.isDangerous(trex)}`);
58console.log(`Zagroda ${enclosure.name}: ${Park.getEnclosureStatus(enclosure)}`);Mechanizmy organizacji typów w TypeScript to potężne narzędzia do budowania dużych, skalowalnych systemów:
Jak mówi Dennis Nedry: "System musi być zorganizowany jak DNA dinozaura - każdy gen na swoim miejscu, ale wszystkie współpracują w jednym organizmie. Namespaces to chromosomy, declaration merging to ekspresja genów, a ambient types to informacja o środowisku."