Kurs JavaScript i TypeScript · Moduł 6: Podstawy TypeScript

Konfiguracja TypeScript i narzędzia

5 min czytania
W tej lekcji7

W Parku Jurajskim nawet najlepszy system genetyczny jest bezużyteczny bez właściwej kalibracji sprzętu laboratoryjnego. Tak samo TypeScript bez odpowiedniej konfiguracji nie wykorzysta swojego pełnego potencjału. Plik tsconfig.json to Twój panel sterowania - definiuje, jak kompilator TypeScript interpretuje i transformuje Twój kod.

tsconfig.json - centrum dowodzenia

Każdy projekt TypeScript zaczyna się od pliku tsconfig.json w katalogu głównym. To plik JSON definiujący opcje kompilatora, które pliki mają być kompilowane i jak ma wyglądać wynik:

1{
2  "compilerOptions": {
3    "target": "ES2020",
4    "module": "ESNext",
5    "lib": ["ES2020", "DOM", "DOM.Iterable"],
6    "outDir": "./dist",
7    "rootDir": "./src",
8    "strict": true,
9    "esModuleInterop": true,
10    "skipLibCheck": true,
11    "forceConsistentCasingInFileNames": true
12  },
13  "include": ["src/**/*"],
14  "exclude": ["node_modules", "dist"]
15}

Kluczowe opcje kompilatora

Target - wersja JavaScript na wyjściu:

  • ES5 - kompatybilność ze starszymi przeglądarkami
  • ES2020 - nowoczesne środowiska (nullish coalescing, optional chaining)
  • ESNext - najnowsze funkcje

Module - system modułów:

  • CommonJS - Node.js (require/module.exports)
  • ESNext - moduły ES (import/export)
  • NodeNext - Node.js z obsługą ESM

Lib - jakie API są dostępne:

  • DOM - API przeglądarki (document, window)
  • ES2020 - metody jak Promise.allSettled, BigInt
  • WebWorker - API Web Workers

Strict Mode - maksymalne bezpieczeństwo

Flaga strict: true włącza wszystkie rygorystyczne sprawdzenia jednocześnie. To jak włączenie pełnych zabezpieczeń w Parku Jurajskim - żaden dinozaur nie wymknie się z zagrody:

1// strict: true włącza WSZYSTKIE poniższe:
2{
3  "strictNullChecks": true,       // null/undefined muszą być obsłużone
4  "strictFunctionTypes": true,    // ścisłe typowanie parametrów funkcji
5  "strictBindCallApply": true,    // ścisłe typowanie bind/call/apply
6  "strictPropertyInitialization": true, // właściwości muszą być zainicjalizowane
7  "noImplicitAny": true,          // zakaz niejawnego any
8  "noImplicitThis": true,         // this musi mieć typ
9  "alwaysStrict": true,           // "use strict" w każdym pliku
10  "useUnknownInCatchVariables": true // catch(e) to unknown, nie any
11}

Przykład różnicy z strictNullChecks:

1// BEZ strictNullChecks - błąd w runtime!
2function getDinoName(id: string): string {
3  const dino = dinosaurs.find(d => d.id === id);
4  return dino.name; // Runtime error: Cannot read property 'name' of undefined
5}
6
7// Z strictNullChecks - TypeScript wymusza obsługę null
8function getDinoName(id: string): string | undefined {
9  const dino = dinosaurs.find(d => d.id === id);
10  return dino?.name; // Bezpieczne - zwraca undefined jeśli nie znaleziono
11}

Path Aliases - skróty nawigacyjne

W dużych projektach importy mogą stać się nieczytelne. Path aliases pozwalają tworzyć skróty:

1{
2  "compilerOptions": {
3    "baseUrl": ".",
4    "paths": {
5      "@models/*": ["src/models/*"],
6      "@utils/*": ["src/utils/*"],
7      "@services/*": ["src/services/*"],
8      "@config": ["src/config/index.ts"]
9    }
10  }
11}

Zamiast:

1import { Dinosaur } from '../../../models/dinosaur';
2import { formatDate } from '../../utils/helpers';

Piszesz:

1import { Dinosaur } from '@models/dinosaur';
2import { formatDate } from '@utils/helpers';

Declaration Files (.d.ts)

Pliki deklaracji opisują typy dla kodu JavaScript. Mają rozszerzenie .d.ts i nie zawierają implementacji - tylko sygnatury typów:

1// dinosaur.d.ts - deklaracja typów
2declare interface IDinosaur {
3  id: string;
4  name: string;
5  species: string;
6  dangerLevel: 1 | 2 | 3 | 4 | 5;
7}
8
9declare function findDinosaur(id: string): IDinosaur | null;
10declare const PARK_NAME: string;
11
12// Deklaracja modułu dla biblioteki JS bez typów
13declare module 'dino-tracker' {
14  export function trackPosition(id: string): [number, number];
15  export function getStatus(id: string): 'active' | 'sleeping' | 'escaped';
16}

Trzy rodzaje plików deklaracji:

  1. Automatyczne - TypeScript generuje je z flagą declaration: true
  2. Ręczne - piszesz sam dla kodu JS bez typów
  3. DefinitelyTyped - społecznościowe typy dla popularnych bibliotek

DefinitelyTyped (@types/*)

Wiele bibliotek JavaScript nie ma wbudowanych typów. Repozytorium DefinitelyTyped zawiera typy tworzone przez społeczność:

1# Instalacja typów dla popularnych bibliotek
2npm install --save-dev @types/node
3npm install --save-dev @types/express
4npm install --save-dev @types/lodash
5npm install --save-dev @types/jest

TypeScript automatycznie rozpoznaje pakiety @types/* - nie musisz ich importować. Jeśli biblioteka ma wbudowane typy (jak axios czy date-fns), nie potrzebujesz osobnego pakietu @types.

Opcja typeRoots w tsconfig kontroluje, skąd TypeScript ładuje typy:

1{
2  "compilerOptions": {
3    "typeRoots": ["./node_modules/@types", "./src/types"]
4  }
5}

ESLint z TypeScript

TypeScript ma własne sprawdzenia typów, ale ESLint z pluginem @typescript-eslint dodaje reguły stylistyczne i zaawansowane analizy:

1{
2  "parser": "@typescript-eslint/parser",
3  "plugins": ["@typescript-eslint"],
4  "extends": [
5    "eslint:recommended",
6    "plugin:@typescript-eslint/recommended",
7    "plugin:@typescript-eslint/recommended-requiring-type-checking"
8  ],
9  "parserOptions": {
10    "project": "./tsconfig.json"
11  },
12  "rules": {
13    "@typescript-eslint/no-explicit-any": "warn",
14    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
15    "@typescript-eslint/prefer-nullish-coalescing": "error",
16    "@typescript-eslint/prefer-optional-chain": "error"
17  }
18}

Najważniejsze reguły @typescript-eslint:

  • no-explicit-any - ostrzega przed użyciem any
  • no-unused-vars - wykrywa nieużywane zmienne (z obsługą TypeScript)
  • prefer-nullish-coalescing - preferuj ?? zamiast ||
  • prefer-optional-chain - preferuj a?.b zamiast a && a.b
  • strict-boolean-expressions - zabrania niejawnych konwersji boolean

Opcje jakości kodu

Dodatkowe flagi kompilatora pomagające utrzymać jakość kodu:

1{
2  "compilerOptions": {
3    "noUnusedLocals": true,        // błąd przy nieużywanych zmiennych
4    "noUnusedParameters": true,    // błąd przy nieużywanych parametrach
5    "noImplicitReturns": true,     // każda ścieżka musi zwracać wartość
6    "noFallthroughCasesInSwitch": true, // wymusza break w switch
7    "noUncheckedIndexedAccess": true,   // arr[i] to T | undefined
8    "exactOptionalPropertyTypes": true  // undefined !== pominięcie pola
9  }
10}

Opcja noUncheckedIndexedAccess jest szczególnie przydatna - zabezpiecza przed błędami indeksowania:

1const dinos = ["T-Rex", "Raptor", "Triceratops"];
2
3// BEZ noUncheckedIndexedAccess
4const first: string = dinos[0]; // OK, ale dinos[100] też byłoby "string"!
5
6// Z noUncheckedIndexedAccess
7const first: string | undefined = dinos[0]; // TypeScript wymusza sprawdzenie
8if (first) {
9  console.log(first.toUpperCase()); // Bezpieczne
10}
Kod do tej lekcji: index.ts
1// Konfiguracja TypeScript - Laboratorium Parku Jurajskiego
2
3// === 1. STRICT MODE W AKCJI ===
4
5// strictNullChecks - wymusza obsluge null/undefined
6interface IDinosaur {
7  id: string;
8  name: string;
9  species: string;
10  dangerLevel: 1 | 2 | 3 | 4 | 5;
11}
12
13const dinosaurs: IDinosaur[] = [
14  { id: "001", name: "Rexy", species: "T-Rex", dangerLevel: 5 },
15  { id: "002", name: "Blue", species: "Velociraptor", dangerLevel: 4 },
16  { id: "003", name: "Bumpy", species: "Ankylosaurus", dangerLevel: 2 },
17];
18
19// Bezpieczne wyszukiwanie z obsluga undefined
20function findDino(id: string): IDinosaur | undefined {
21  return dinosaurs.find(d => d.id === id);
22}
23
24const dino = findDino("001");
25if (dino) {
26  console.log("Znaleziono:", dino.name, "- poziom zagrozenia:", dino.dangerLevel);
27} else {
28  console.log("Nie znaleziono dinozaura");
29}
30
31// === 2. PATH ALIASES (symulacja) ===
32// W prawdziwym projekcie:
33// import { Dinosaur } from '@models/dinosaur';
34// import { formatDate } from '@utils/helpers';
35// Zamiast: import { Dinosaur } from '../../../models/dinosaur';
36
37// === 3. DECLARATION FILES (.d.ts) ===
38// Symulacja deklaracji typow dla biblioteki JS
39
40// Tak wygladalby plik dino-tracker.d.ts:
41// declare module 'dino-tracker' {
42//   export function trackPosition(id: string): [number, number];
43//   export function getStatus(id: string): DinoStatus;
44// }
45
46// Uzywamy typow tak, jakby istniala biblioteka:
47type DinoStatus = "active" | "sleeping" | "escaped";
48
49function getStatus(id: string): DinoStatus {
50  const dino = findDino(id);
51  if (!dino) return "escaped";
52  return dino.dangerLevel >= 4 ? "active" : "sleeping";
53}
54
55console.log("Status Rexy:", getStatus("001"));
56console.log("Status Blue:", getStatus("002"));
57
58// === 4. noUncheckedIndexedAccess ===
59const species = ["T-Rex", "Raptor", "Triceratops"];
60
61// Z noUncheckedIndexedAccess: species[0] to string | undefined
62const first = species[0];
63if (first !== undefined) {
64  console.log("Pierwszy gatunek:", first.toUpperCase());
65}
66
67// === 5. DEFINITELYTYPED ===
68// Instalacja typow:
69// npm install --save-dev @types/node @types/express @types/lodash
70// TypeScript automatycznie je rozpoznaje
71
72// === 6. ESLINT Z TYPESCRIPT ===
73// Przyklady regul @typescript-eslint:
74
75// prefer-nullish-coalescing: uzywaj ?? zamiast ||
76const parkName: string | null = null;
77const displayName = parkName ?? "Jurassic Park"; // lepsze niz ||
78console.log("Park:", displayName);
79
80// prefer-optional-chain: uzywaj ?. zamiast &&
81interface ParkConfig {
82  security?: {
83    fences?: {
84      voltage?: number;
85    };
86  };
87}
88
89const config: ParkConfig = { security: { fences: { voltage: 10000 } } };
90const voltage = config.security?.fences?.voltage; // lepsze niz config && config.security && ...
91console.log("Napiecie ogrodzenia:", voltage, "V");
92
93// noImplicitReturns: kazda sciezka musi zwracac wartosc
94function classifyThreat(level: number): string {
95  if (level >= 4) return "WYSOKIE ZAGROZENIE";
96  if (level >= 2) return "SREDNIE ZAGROZENIE";
97  return "NISKIE ZAGROZENIE"; // wymagane przez noImplicitReturns
98}
99
100console.log("Klasyfikacja (5):", classifyThreat(5));
101console.log("Klasyfikacja (1):", classifyThreat(1));

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Którą z poniższych flag NIE włącza opcja "strict": true w tsconfig.json?

  2. 2. Jaka jest rola plików .d.ts (declaration files) w TypeScript?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Napraw problemy ze strict mode: strictNullChecks, noUncheckedIndexedAccess, noImplicitReturns i skonfiguruj path aliases.

  • Edytor kodu

    Napisz interfejs SecurityEvent, zaimplementuj checkPerimeter i logEvent, oraz stwórz typ DinoSize z funkcją classifyBySize.

  • Układanie w pionie

    Uporządkuj opcje tsconfig od najważniejszych (włączaj najpierw) do zaawansowanych:

  • Klikanie w kolejności

    Ułóż elementy konfiguracji path alias w tsconfig.json:

  • Układanie w pionie

    Uporządkuj kroki tworzenia nowego projektu TypeScript:

Przydatne artykuły