Moduły — porządek w kilku plikach
Oddziel model gracza od punktacji i uruchomienia programu. Importy pokażą, kto z czego korzysta, zamiast chować zależności w jednym długim pliku.
Po tej lekcji potrafisz
- Podzielisz program na pliki z export i import.
- Odróżnisz import wartości od import type.
- Uruchomisz moduły ESM po kompilacji w Node.js.
Jeden plik robi się za długi
W pliku zaczynają mieszać się modele, punktacja i raport. Trudniej znaleźć właściwe miejsce zmiany. Moduł udostępnia wybrane elementy przez export, a jego odbiorca jawnie zapisuje zależność przez import.
Małe kroki, konkretne efekty
Eksport nazwany i import
Ten przykład składa się z dwóch plików. Utwórz je w src i uruchom npm start ze środowiska lekcji 02: package.json ma type: module, a tsconfig używa NodeNext. W imporcie wpisujemy .js, ponieważ taki plik powstanie po kompilacji.
export function podwoj(n: number): number { return n * 2; }
import { podwoj } from "./punkty.js";
console.log(podwoj(6));
12
TypeScript podczas sprawdzania odnajdzie źródłowy punkty.ts. Node uruchomi wynikowy punkty.js. Nie uruchamiaj źródeł poleceniem node src/index.ts w tym ćwiczeniu.
Typ nie jest wartością runtime
import type sprowadza wyłącznie opis typu i znika z JavaScriptu. Zwykły import sprowadza funkcję, stałą albo klasę potrzebną podczas działania.
export interface Gracz { nick: string; punkty: number }
import type { Gracz } from "./model.js";
const g: Gracz = { nick: "Ala", punkty: 5 };
console.log(g.nick);
Ala
Wszystkie pliki danego przykładu należą do jednego projektu. Przy przejściu do kolejnego przykładu nie sklejaj definicji w jeden index.ts.
Mini ranking w trzech plikach
Utwórz poniższe trzy pliki w src. Model opisuje dane, punktacja wykonuje obliczenia, a index organizuje uruchomienie. To kompletny program modułowy.
export interface Gracz { nick: string; wygrane: number; remisy: number }
import type { Gracz } from "./gracz.js";
export function punkty(g: Gracz): number { return g.wygrane * 3 + g.remisy; }
export function opis(g: Gracz): string { return g.nick + ": " + punkty(g); }
import type { Gracz } from "./gracz.js";
import { opis } from "./ranking.js";
const gracze: Gracz[] = [
{ nick: "Ala", wygrane: 3, remisy: 1 },
{ nick: "Neo", wygrane: 2, remisy: 2 }
];
for (const gracz of gracze) { console.log(opis(gracz)); }
Ala: 10
Neo: 8
Jak program dochodzi do wyniku
- gracz.ts nie musi znać rankingu ani sposobu wypisywania.
- ranking.ts importuje model wyłącznie do sprawdzania typu.
- index.ts importuje działającą funkcję opis oraz osobno typ Gracz.
- Strzałki zależności prowadzą w jednym kierunku; moduły nie importują się nawzajem w kółko.
Zanim przejdziesz dalej
Czy trzeba eksportować każdą pomocniczą zmienną?
Sprawdź swoje rozumowanie
Nie. Eksportuj tylko publiczną część modułu. Reszta może pozostać szczegółem jego implementacji.
Zmień kod i zobacz, co się stanie
Alias i eksport domyślny
Eksport nazwany można lokalnie przemianować przez as. Eksport domyślny importuje się bez klamer i można nadać mu lokalną nazwę. W kursie preferujemy nazwane eksporty, żeby nazwy były spójne między plikami.
export const separator = " | ";
export default function formatuj(n: number): string { return n + " pkt"; }
import formatuj, { separator as przerwa } from "./format.js";
console.log(formatuj(10) + przerwa + formatuj(20));
10 pkt | 20 pkt
as w imporcie zmienia lokalną nazwę — to nie jest asercja typu. Jedna składnia słowa kluczowego ma tutaj inne znaczenie.
Gdy wynik zaskakuje
| Pomyłka | Co się dzieje i jak to poprawić |
|---|---|
| Brak ./ przy lokalnym imporcie | Node potraktuje nazwę jak nazwę pakietu. |
| Pomijanie .js w tych modułach ESM | Uruchamiany Node potrzebuje właściwej ścieżki wynikowego pliku. |
| import type użyty do tworzenia instancji | Klasa potrzebna do new musi być importowana jako wartość. |
Teraz Twoja kolej
Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.
Wyeksportuj LIMIT = 16 z ustawienia.ts i wypisz ją w index.ts.
Mała podpowiedź
Eksport nazwany wymaga klamer w imporcie.
Przykładowe rozwiązanie z wyjaśnieniem
export const LIMIT = 16;
import { LIMIT } from "./ustawienia.js";
console.log(LIMIT);
16
Konfiguracja ma jedno źródło wartości.
Przenieś funkcję sekundy do czas.ts. W index.ts wypisz wynik dla 3 minut.
Mała podpowiedź
Funkcja musi mieć export.
Przykładowe rozwiązanie z wyjaśnieniem
export function sekundy(minuty: number): number { return minuty * 60; }
import { sekundy } from "./czas.js";
console.log(sekundy(3));
180
Importowany moduł może zawierać same obliczenia, bez uruchamiania raportu.
Bilet ma id i cene. Wyeksportuj interfejs, sprowadź import type i utwórz bilet 1 o cenie 20.
Mała podpowiedź
Interfejs nie jest konstruktorem.
Przykładowe rozwiązanie z wyjaśnieniem
export interface Bilet { id: number; cena: number }
import type { Bilet } from "./bilet.js";
const b: Bilet = { id: 1, cena: 20 };
console.log(b.id, b.cena);
1 20
Opis danych można współdzielić bez tworzenia obiektu przez new.
Dwa moduły eksportują opis. Jeden dotyczy gracza, drugi drużyny. Zaimportuj oba pod różnymi nazwami i wypisz wyniki.
Mała podpowiedź
Użyj as w obu importach.
Przykładowe rozwiązanie z wyjaśnieniem
export function opis(): string { return "Gracz"; }
export function opis(): string { return "Drużyna"; }
import { opis as opisGracza } from "./osoba.js";
import { opis as opisDruzyny } from "./druzyna.js";
console.log(opisGracza(), opisDruzyny());
Gracz Drużyna
Lokalne aliasy rozwiązują konflikt bez zmieniania modułów.
Co zabierasz do następnej lekcji
- export udostępnia element, import określa zależność.
- import type dotyczy opisu znikającego po kompilacji.
- Ścieżki modułów muszą pasować do wybranego sposobu uruchamiania.