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.

CEL LEKCJI

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

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.

POZNAJ SKŁADNIĘ

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.

src/punkty.ts — plik tego projektu
export function podwoj(n: number): number { return n * 2; }
src/index.ts — osobny przykład
import { podwoj } from "./punkty.js";
console.log(podwoj(6));
wynik w konsoli
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.

src/model.ts — plik tego projektu
export interface Gracz { nick: string; punkty: number }
src/index.ts — osobny przykład
import type { Gracz } from "./model.js";
const g: Gracz = { nick: "Ala", punkty: 5 };
console.log(g.nick);
wynik w konsoli
Ala

Wszystkie pliki danego przykładu należą do jednego projektu. Przy przejściu do kolejnego przykładu nie sklejaj definicji w jeden index.ts.

PRZYKŁAD Z OMÓWIENIEM

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.

src/gracz.ts — plik tego projektu
export interface Gracz { nick: string; wygrane: number; remisy: number }
src/ranking.ts — plik tego projektu
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); }
src/index.ts — osobny przykład
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)); }
wynik w konsoli
Ala: 10
Neo: 8

Jak program dochodzi do wyniku

  1. gracz.ts nie musi znać rankingu ani sposobu wypisywania.
  2. ranking.ts importuje model wyłącznie do sprawdzania typu.
  3. index.ts importuje działającą funkcję opis oraz osobno typ Gracz.
  4. 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.

EKSPERYMENT

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.

src/format.ts — plik tego projektu
export const separator = " | ";
export default function formatuj(n: number): string { return n + " pkt"; }
src/index.ts — osobny przykład
import formatuj, { separator as przerwa } from "./format.js";
console.log(formatuj(10) + przerwa + formatuj(20));
wynik w konsoli
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.

CZĘSTE BŁĘDY

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ść.
ZADANIA

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.

ZAD 1Stała w osobnym pliku★☆☆

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
src/ustawienia.ts — plik tego projektu
export const LIMIT = 16;
src/index.ts — osobny przykład
import { LIMIT } from "./ustawienia.js";
console.log(LIMIT);
wynik w konsoli
16

Konfiguracja ma jedno źródło wartości.

ZAD 2Konwerter czasu★★☆

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
src/czas.ts — plik tego projektu
export function sekundy(minuty: number): number { return minuty * 60; }
src/index.ts — osobny przykład
import { sekundy } from "./czas.js";
console.log(sekundy(3));
wynik w konsoli
180

Importowany moduł może zawierać same obliczenia, bez uruchamiania raportu.

ZAD 3Wspólny model biletu★★☆

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
src/bilet.ts — plik tego projektu
export interface Bilet { id: number; cena: number }
src/index.ts — osobny przykład
import type { Bilet } from "./bilet.js";
const b: Bilet = { id: 1, cena: 20 };
console.log(b.id, b.cena);
wynik w konsoli
1 20

Opis danych można współdzielić bez tworzenia obiektu przez new.

ZAD 4Dwie funkcje o tej samej nazwie★★★

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
src/osoba.ts — plik tego projektu
export function opis(): string { return "Gracz"; }
src/druzyna.ts — plik tego projektu
export function opis(): string { return "Drużyna"; }
src/index.ts — osobny przykład
import { opis as opisGracza } from "./osoba.js";
import { opis as opisDruzyny } from "./druzyna.js";
console.log(opisGracza(), opisDruzyny());
wynik w konsoli
Gracz Drużyna

Lokalne aliasy rozwiązują konflikt bez zmieniania modułów.

PODSUMOWANIE

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.