Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Namespace, declaration merging i ambient types

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

Namespaces - przestrzenie nazw

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

Zagnieżdżone namespaces

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 - łączenie deklaracji

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.

Interface merging

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`);

Namespace merging

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 merging z klasą lub funkcją

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`);

Ambient declarations - deklaracje otoczenia

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 .d.ts - pliki deklaracji typów

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}

Rozszerzanie istniejących modułów (module augmentation)

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

Praktyczny przykład - organizacja systemu Parku

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

Podsumowanie

Mechanizmy organizacji typów w TypeScript to potężne narzędzia do budowania dużych, skalowalnych systemów:

  1. Namespaces - grupowanie powiązanych typów i funkcji, unikanie kolizji nazw
  2. Declaration merging - rozszerzanie istniejących interfejsów i namespace'ów o nowe pola i metody
  3. Ambient declarations (declare) - opisywanie typów dla kodu zdefiniowanego gdzie indziej
  4. Pliki .d.ts - pliki zawierające wyłącznie deklaracje typów (bez kodu)
  5. Module augmentation - rozszerzanie typów w istniejących modułach (np. Express, biblioteki zewnętrzne)

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

Ir a CodeWorlds