TypeScript to silnie typowany nadzbiór języka JavaScript opracowany przez Microsoft. Kod TS jest transpilowany do JS przez kompilator tsc, więc działa w każdym środowisku obsługującym JavaScript.
Kluczowe różnice: statyczna analiza typów w czasie kompilacji (błędy widoczne w IDE przed uruchomieniem), interfejsy i typy, dekoratory, generyki oraz pełna obsługa nowoczesnego ES2015+ z downcasterem. W runtime TypeScript nie istnieje — pozostaje czysty JS.
interface a type w TypeScript?
✓
▶
interface służy do opisywania kształtu obiektów i może być rozszerzana (extends) oraz scalana przez deklarację merging — czyli dwie deklaracje tego samego interfejsu automatycznie się łączą.
type (alias) jest bardziej ogólny: może reprezentować unię (A | B), krotkę, typ prymitywny lub mapped type. Nie obsługuje merging. W praktyce: używaj interface dla publicznego API bibliotek i klas, type dla złożonych typów, unii i aliasów.
any, unknown i never — kiedy ich używać?
✓
▶
any wyłącza kontrolę typów — zmienna może być czymkolwiek bez żadnych sprawdzeń. Używaj tylko w ostateczności (migracja JS → TS, zewnętrzne biblioteki bez typów).
unknown to bezpieczna alternatywa dla any. Możesz przypisać do niej cokolwiek, ale przed użyciem musisz zwęzić typ przez type guard (typeof, instanceof). Zalecany w funkcjach przyjmujących dane zewnętrzne.
never reprezentuje typ niemożliwy do osiągnięcia — np. funkcja rzucająca zawsze wyjątek lub nieskończona pętla. Pomocny w exhaustiveness checking w switch/case.
Type narrowing to mechanizm, w którym kompilator TypeScript zawęża typ zmiennej na podstawie warunków w kodzie. Po sprawdzeniu warunków TypeScript „wie", jaki konkretny typ jest w danej gałęzi.
Techniki zwężania: typeof (typeof x === 'string'), instanceof, in operator ('prop' in obj), type predicates (x is Type), discriminated union (wspólne pole literalne) oraz assertion functions.
readonly i const — jaka jest między nimi różnica?
✓
▶
const to deklaracja zmiennej w JavaScript — blokuje ponowne przypisanie referencji, ale nie chroni zawartości obiektu (właściwości wciąż można modyfikować).
readonly to modyfikator TypeScript dla właściwości obiektów, interfejsów i klas — blokuje modyfikację danego pola po inicjalizacji. Istnieje też utility type Readonly<T> zwracający wersję interfejsu ze wszystkimi polami readonly.
Typy literalne pozwalają zawęzić typ do konkretnych wartości zamiast ogólnych prymitywów. Mogą to być stringi, liczby lub wartości logiczne.
Najczęstsze zastosowanie to string literal unions zastępujące enumy oraz const assertion (as const), która zamraża strukturę obiektu do typów literalnych. Pozwala to TypeScriptowi wykrywać niedozwolone wartości w czasie kompilacji.
enum) w TypeScript i kiedy unikać ich używania?
✓
▶
enum to specjalna struktura TypeScript generująca obiekt JS w runtime. Domyślnie numeryczny (0, 1, 2…), może być też const enum (wbudowany inline, bez obiektu w output) lub string enum.
Kiedy unikać: numeric enum umożliwia przypisanie dowolnej liczby (brak type safety), const enum jest problematyczny z izolowanymi modułami (Babel, esbuild). Alternatywa: as const + string literal union — czyste TS, zero narzutu w runtime, pełna type safety.
Type guard to wyrażenie, które w runtime sprawdza typ i jednocześnie informuje kompilator o zawężeniu. Wbudowane: typeof, instanceof, in.
Własny type guard to funkcja zwracająca type predicate: parameterName is Type. Jeśli funkcja zwróci true, TypeScript traktuje parametr jako dany typ w danej gałęzi. Używaj gdy dane zewnętrzne (API, JSON) muszą zostać zweryfikowane i bezpiecznie opatrzone typem.
TypeScript stosuje structural typing — kompatybilność typów wynika z kształtu (struktury) obiektu, nie z nazwy klasy czy interfejsu. Jeśli obiekt ma wszystkie wymagane właściwości, jest kompatybilny z danym typem.
Różni się to od nominal typing (C#, Java), gdzie klasy muszą explicite deklarować implementację interfejsu. W TypeScript wystarczy, że obiekt pasuje strukturalnie — nazywane to bywa „duck typing": jeśli chodzi jak kaczka i kwacze jak kaczka, to jest kaczką.
Union type (A | B) oznacza, że wartość może być jednym z podanych typów. Wymaga type narrowing przed użyciem specyficznych metod. Typowe zastosowanie: parametry funkcji przyjmujące wiele typów, odpowiedzi API z różnymi kształtami.
Intersection type (A & B) łączy typy — wynikowy obiekt musi spełniać oba jednocześnie. Używany do kompozycji interfejsów (mixiny) i rozszerzania typów bez dziedziczenia.
Discriminated union (tagged union) to wzorzec, w którym każdy wariant unii posiada wspólne pole literalne (discriminant) umożliwiające jednoznaczne rozróżnienie. TypeScript automatycznie zawęża typ w każdej gałęzi switch/if.
Wzorzec jest szczególnie przydatny przy modelowaniu stanów aplikacji (loading / success / error), akcji Redux/Zustand oraz wyników operacji asynchronicznych. Razem z never umożliwia exhaustiveness checking — kompilator ostrzega, gdy nie obsłużono wszystkich wariantów.
Mapped types pozwalają tworzyć nowe typy poprzez iterację po kluczach innego typu za pomocą składni { [K in keyof T]: ... }. Na tym mechanizmie opierają się wbudowane utility types.
Można dodawać lub usuwać modyfikatory (+readonly, -readonly, +?, -?) oraz transformować typy właściwości. Przydatne do tworzenia wersji obiektów tylko do odczytu, opcjonalnych, nullowalnych itp.
Conditional types działają jak operator trójargumentowy na poziomie typów: T extends U ? X : Y. Jeśli T jest przypisywalny do U, wynikiem jest X, w przeciwnym razie Y.
Kluczową funkcją jest distributivity — stosowane na unii działają na każdym wariancie osobno. Razem ze słowem kluczowym infer pozwalają wyciągać fragmenty typów (np. typ zwracany funkcji, parametry). Na tym opierają się ReturnType, Parameters, Awaited.
Template literal types (TS 4.1+) pozwalają budować typy stringowe z innych typów przy użyciu składni szablonów: `prefix_${T}`. Stosowane na unii stringów tworzą unię wszystkich kombinacji.
Zastosowania: generowanie kluczy zdarzeń (`on${EventName}`), walidacja formatów (np. `${number}px`), typowane ścieżki CSS, ścieżki obiektów JSON (dot notation). Razem z Capitalize, Uppercase i innymi intrinsic types tworzą zaawansowane API.
Dekoratory to specjalne funkcje poprzedzone @ stosowane do klas, metod, właściwości lub parametrów. Umożliwiają modyfikację zachowania w stylu aspect-oriented programming.
Dekoratory klas są powszechne w Angular (@Component, @Injectable) i NestJS (@Controller, @Get). Od TypeScript 5.0 obowiązuje nowa specyfikacja TC39 Stage 3 — różni się od starszego API eksperymentalnego. Wymagają włączenia experimentalDecorators (legacy) lub są dostępne natywnie w TS 5+.
keyof i typeof na poziomie typów?
✓
▶
keyof T zwraca unię wszystkich kluczy typu T jako typy literalne. Używany do tworzenia funkcji bezpiecznie odwołujących się do właściwości obiektu (K extends keyof T).
typeof użyty w pozycji typowej zwraca typ wartości — różni się od runtime typeof. Pozwala inferować typy z istniejących obiektów i funkcji bez duplikowania definicji. Razem tworzą potężne kombinacje: keyof typeof config.
Index signature ([key: string]: ValueType) opisuje obiekty o dynamicznych kluczach — słowniki, mapy, rekordy konfiguracyjne. Wszystkie wartości muszą pasować do podanego typu.
Ograniczenia: indeks string powoduje, że TypeScript traktuje wszystkie właściwości jako mogące być undefined. Bezpieczniejszą alternatywą jest utility type Record<K, V> lub Map<K, V>. Przy noUncheckedIndexedAccess: true kompilator wymusza sprawdzanie undefined przy dostępie przez indeks.
Function overloads pozwalają zdefiniować kilka sygnatur tej samej funkcji dla różnych kombinacji typów parametrów i zwracanych wartości. TypeScript wybiera właściwą sygnaturę w zależności od wywołania.
Składnia: przed implementacją deklaruje się sygnatury przeciążeń (bez ciała). Implementacja ma najszerszą sygnaturę i jest ukryta przed konsumentami API. Dobrze zastępuje skomplikowane union zwracane przez funkcję w jednej sygnaturze.
TypeScript oferuje trzy modyfikatory: public (domyślny — dostępny wszędzie), protected (dostępny w klasie i jej potomkach), private (tylko w klasie). Wszystkie są sprawdzane wyłącznie w czasie kompilacji.
Od TypeScript 4.3 dostępny jest też prywatny identyfikator JS (#pole) — to prawdziwa prywatność w runtime, niemożliwa do ominięcia przez rzutowanie. Różni się od private: jest nieosiągalny nawet z zewnątrz przez as any.
Klasa abstrakcyjna (abstract class) nie może być bezpośrednio instancjonowana. Może zawierać implementacje metod (shared logic) oraz abstrakcyjne deklaracje, które muszą być zaimplementowane przez klasy pochodne.
Różnice od interfejsu: klasa abstrakcyjna istnieje w runtime jako konstruktor (można sprawdzić instanceof), może przechowywać stan i implementować metody. Interfejs jest czysto typowy — znika po kompilacji. Używaj klasy abstrakcyjnej gdy potrzebujesz wspólnej logiki; interfejsu gdy opisujesz kontrakt.
Mixiny to wzorzec kompozycji, który pozwala łączyć zachowania z wielu źródeł bez wielokrotnego dziedziczenia (TypeScript nie obsługuje extends A, B). Mixin to funkcja przyjmująca klasę bazową i zwracająca nową klasę ją rozszerzającą.
Implementacja opiera się na typie Constructor<T> = new (...args: any[]) => T. Każdy mixin jest funkcją wyższego rzędu. Deklaracje interfejsów scalają się z klasą, dostarczając pełną type safety. Popularny wzorzec w bibliotekach i frameworkach.
get i set definiują akcesory pozwalające na wykonanie logiki przy odczycie lub zapisie właściwości. Z zewnątrz wyglądają jak zwykłe pole, wewnętrznie mogą walidować, obliczać lub logować.
Zastosowania: lazy initialization (obliczanie wartości przy pierwszym dostępie), walidacja danych wejściowych, właściwości pochodne (computed). Uwaga: getter bez settera tworzy właściwość tylko do odczytu. TypeScript wymaga zgodności typów między getterem a setterem od TS 4.3.
TypeScript pozwala deklarować i inicjalizować właściwości klasy bezpośrednio w parametrach konstruktora przez dodanie modyfikatora (public, private, protected, readonly). Eliminuje powtarzalny boilerplate.
super?
✓
▶
TypeScript obsługuje jednokrotne dziedziczenie klas przez extends. Klasa pochodna dziedziczy publiczne i chronione pola oraz metody klasy bazowej. Możliwe jest nadpisanie metod (override) z opcjonalnym słowem kluczowym override (TS 4.3).
super odwołuje się do klasy bazowej: super() wywołuje konstruktor rodzica (wymagane w konstruktorze klasy pochodnej przed użyciem this), super.metodA() wywołuje metodę bazową. Flaga noImplicitOverride wymusza jawne oznaczenie nadpisanych metod.
Klasa może implementować wiele interfejsów przez implements A, B, C. TypeScript sprawdza, czy klasa dostarcza wszystkich wymaganych właściwości i metod z każdego interfejsu. To wyłącznie kontrola typów — brak runtime efektu.
Ważne: implements nie dziedziczy implementacji — to kontrakt, nie mixin. Klasa musi samodzielnie dostarczyć każdą metodę interfejsu. Interfejsy mogą też rozszerzać się nawzajem (interface C extends A, B).
this w TypeScript — jak typować kontekst i unikać błędów?
✓
▶
TypeScript pozwala typować this jako pierwszy (fałszywy) parametr funkcji: function fn(this: MyClass, ...). Kompilator sprawdza, że funkcja jest wywoływana z właściwym kontekstem. Flaga noImplicitThis wymusza jawne typowanie this gdy nie jest jasny.
Typowe problemy: metody klas przekazywane jako callbacki tracą kontekst. Rozwiązania: strzałkowe metody (method = () => {}) lub jawne bindowanie. TypeScript oferuje też typ ThisType<T> do zaawansowanego typowania obiektów konfiguracyjnych.
Singleton gwarantuje istnienie jednej instancji klasy. W TypeScript: konstruktor private (blokuje new z zewnątrz) + prywatna statyczna właściwość instance + publiczna statyczna metoda getInstance().
Alternatywa: moduły ES (import singleton — każdy import modułu zwraca tę samą instancję) lub kontenery DI (NestJS, InversifyJS).
Generyki pozwalają pisać komponenty (funkcje, klasy, interfejsy) pracujące z różnymi typami bez utraty type safety. Zamiast any używa się parametrów typowych (<T>), które są podstawiane w miejscu wywołania.
Zalety: reużywalność kodu, zachowanie informacji o typie (inaczej niż przy any), type inference — kompilator często sam wydedukuje typ bez jawnego podawania. Generyki są fundamentem całego systemu utility types TypeScript.
Generic constraints (T extends SomeType) ograniczają jakie typy mogą być użyte jako parametr generyczny. Pozwalają kompilatorowi wiedzieć, jakie właściwości lub metody są dostępne na T.
Typowe zastosowania: T extends object, T extends keyof U, T extends { length: number }. Constraints można łączyć z extends i &. Klauzula extends w generykach różni się od dziedziczenia klas — to warunek przypisywalności.
TypeScript dostarcza wiele gotowych utility types opartych na mapped types i conditional types:
infer w conditional types?
✓
▶
Słowo kluczowe infer w conditional types pozwala „przechwycić" i nazwać fragment typu, który jest dopasowywany. Używane wyłącznie wewnątrz klauzuli extends conditional type.
To mechanizm stojący za utility types takimi jak ReturnType, Parameters, InstanceType czy Awaited. Pozwala wyciągać dowolnie głęboko zagnieżdżone typy bez dostępu do oryginalnej definicji.
Variadic tuple types (TS 4.0) pozwalają na spread elementów krotki w definicji innej krotki: [...T, string], [boolean, ...T]. TypeScript zachowuje pełną informację o typach przy konkatenacji krotek.
Zastosowania: typowanie funkcji concat, zip, pipelines, currying. Pozwala na precyzyjne typowanie funkcji variadycznych bez uciekania się do overloadów dla każdej liczby argumentów.
Funkcje wyższego rzędu przyjmują lub zwracają inne funkcje. Typowanie opiera się na generykach i typach funkcji: (...args: A) => R. Klucz to zachowanie informacji typowej przez całą transformację.
Przy curryingu i pipelines przydają się variadic tuple types i conditional types do prawidłowego inferowania typów zwracanych w każdym kroku.
Najlepsze podejście to schema validation + type inference: biblioteki jak zod, valibot lub arktype definiują schemę walidującą dane w runtime i automatycznie inferują typy TypeScript.
Unikaj as ResponseType (type assertion bez walidacji) — to fałszywe poczucie bezpieczeństwa. Zamiast tego: zdefiniuj schemat Zod → z.infer<typeof schema> daje typ → parsuj odpowiedź przez schema.parse(data). Błędy walidacji wyrzucane są w runtime, zanim dane trafią do aplikacji. Alternatywa: generowanie typów z OpenAPI (openapi-ts).
Kowariancja — typ pochodny może być użyty tam gdzie oczekiwany jest typ bazowy (pozycja zwracana funkcji). Kontrawariancja — odwrotnie: tam gdzie oczekiwany jest typ bardziej szczegółowy, można podstawić ogólniejszy (pozycja parametrów funkcji).
TypeScript stosuje bivariance dla metod klas (historyczny kompromis), ale strict function types (strictFunctionTypes: true) wprowadza kontrawariancję dla typów funkcji. Oznacza to, że (animal: Animal) => void nie jest przypisywalny tam gdzie oczekiwane (dog: Dog) => void.
satisfies operator w TypeScript?
✓
▶
Operator satisfies (TS 4.9) sprawdza, czy wyrażenie pasuje do podanego typu, ale nie zmienia inferred type zmiennej. Różni się od adnotacji (która narzuca typ) i od as (która nie sprawdza nic).
Praktyczne zastosowanie: weryfikacja konfiguracji zachowując dokładniejszy inferred type. Np. obiekt sprawdzony przez satisfies Record<string, string> zachowuje literalne typy kluczy — IDE oferuje autocomplete na oryginalnych kluczach, a TypeScript pilnuje struktury.
tsconfig.json?
✓
▶
Kluczowe opcje tsconfig.json:
.d.ts) i do czego służą?
✓
▶
Pliki .d.ts to deklaracje typów — zawierają wyłącznie informacje typowe bez implementacji. Pozwalają TypeScriptowi rozumieć kształt bibliotek napisanych w czystym JavaScript.
Źródła: wbudowane w paczkę npm (pole types w package.json), repozytorium DefinitelyTyped (pakiety @types/*) lub wygenerowane przez kompilator TS (declaration: true). Pisząc własną bibliotekę zawsze generuj pliki .d.ts, by konsumenci mieli type safety.
Declaration merging pozwala TypeScriptowi scalać wielokrotne deklaracje tej samej nazwy. Interfejsy o tej samej nazwie automatycznie łączą swoje właściwości. Klasy i interfejsy o tej samej nazwie też się scalają.
Module augmentation umożliwia rozszerzanie typów z zewnętrznych modułów bez modyfikacji oryginałów. Składnia: declare module 'express' { ... }. Typowe zastosowanie: dodawanie właściwości do Request Express, rozszerzanie typów React (np. JSX.IntrinsicElements), augmentacja globalnych typów.
Pliki .tsx obsługują JSX. Typy React dostarczane przez @types/react. Komponenty typuje się przez React.FC<Props> (lub bezpośrednio jako funkcję z typowanymi parametrami — preferowane). Props definiowane przez interface lub type.
Hooki są generyczne: useState<number>, useRef<HTMLDivElement>, useContext<Theme>. useReducer typuje się przez discriminated union akcji. Własne hooki zwracają dobrze otypowane wartości — opcjonalnie as const dla krotek zwracanych z hooków zamiast tablic.
strict mode TypeScript i co obejmuje?
✓
▶
Flaga strict: true w tsconfig włącza całą rodzinę flag naraz:
Zalecana strategia stopniowej migracji: 1) Dodaj tsconfig.json z allowJs: true i checkJs: false — TS kompiluje JS bez błędów. 2) Zmieniaj pliki z .js na .ts jeden po drugim — zacznij od modułów bez zależności (leaf files). 3) Stopniowo zwiększaj rygoryzm — wyłącz allowJs, włącz strict.
Narzędzia: ts-migrate (automatyczne dodawanie any żeby projekt się kompilował), JSDoc types (typy w komentarzach zanim zmienisz rozszerzenie), @ts-check w plikach JS. Unikaj masowego używania any — to dług techniczny do spłacenia.
async/await i typy?
✓
▶
Funkcje async automatycznie zwracają Promise<T>, gdzie T to typ wartości przez nie zwróconej. TypeScript inferuje to automatycznie lub można podać jawnie: async function fetchUser(): Promise<User>.
Utility type Awaited<T> (TS 4.5) rekurencyjnie rozpakowuje zagnieżdżone Promise — przydatny do typowania wyników Promise.all. Przy obsłudze błędów w catch zmienna e jest unknown (strict mode) — wymagane type guard lub instanceof Error.
Project References (TS 3.0) pozwalają podzielić duży projekt na mniejsze podprojekty z oddzielnymi plikami tsconfig.json. Kompilacja jest przyrostowa — tylko zmienione projekty są rekompilowane.
Idealne dla monorepo: oddzielne pakiety (shared, backend, frontend) mogą na siebie wskazywać przez references w tsconfig. Wymaga composite: true w projektach-zależnościach. Narzędzia: Turborepo, Nx, pnpm workspaces + TS project references to sprawdzona kombinacja.
Najczęstsze antypatyczne wzorce w TypeScript: