Tenzan Academy
Motyw
  • 9 sekcji
  • 24 pytania
  • 23 fiszki

TypeScript

System typów TypeScript 5.x: typowanie strukturalne, zawężanie, unie dyskryminowane, generyki, typy warunkowe, mapowane i szablonowe.

TypeScript

System typów TypeScript 5.x: typowanie strukturalne, zawężanie, unie dyskryminowane, generyki, typy warunkowe, mapowane i szablonowe.

Quiz

Typowanie strukturalne i podstawy systemu typów

na egzaminie

TypeScript nie sprawdza typów po nazwie deklaracji, tylko po kształcie danych. To fundament odróżniający go od Javy czy C#, i źródło większości zaskoczeń u programistów przychodzących z języków nominalnie typowanych.

Termin kluczowy

Structural typing (duck typing): Dwa typy są kompatybilne, gdy mają ten sam kształt, czyli te same właściwości o kompatybilnych typach. Nazwa interfejsu czy klasy, którą deklarujesz, nie ma znaczenia.

Kompatybilność strukturalna niezależna od nazwy typu typescript
interface Point2D {
  x: number;
  y: number;
}

interface Vector {
  x: number;
  y: number;
}

function length(p: Point2D): number {
  return Math.sqrt(p.x ** 2 + p.y ** 2);
}

const v: Vector = { x: 3, y: 4 };
length(v); // OK, Vector ma dokładnie kształt Point2D, nazwa nie ma znaczenia

// Excess property check: literał obiektowy jest sprawdzany ściślej niż zmienna
length({ x: 3, y: 4, z: 0 }); // Error: 'z' nie istnieje w Point2D
const withZ = { x: 3, y: 4, z: 0 };
length(withZ); // OK, bo withZ przechodzi przez zmienną (nie fresh object literal)
Excess property checking

Kompilator dodatkowo sprawdza 'świeże' literały obiektowe pod kątem nadmiarowych właściwości, ale to osobny, ostrzejszy mechanizm nałożony na strukturalną kompatybilność, nie jej część. Przypisanie przez zmienną pośrednią omija tę kontrolę.

Pułapka

Programiści z tłem Java/C# zakładają, że interfejs działa jak kontrakt nominalny (trzeba explicit 'implements'). W TS wystarczy pasujący kształt, co ułatwia integrację z bibliotekami trzecimi, ale też pozwala przypadkowo przepuścić obiekt niewłaściwego 'logicznie' typu, jeśli kształt się zgadza.

Termin kluczowy

Type inference: Kompilator wywnioskowuje typ zmiennej z jej inicjalizacji bez jawnej adnotacji. Preferuj inference nad jawnymi adnotacjami tam, gdzie nie tracisz precyzji, kod jest krótszy i typ nadal poprawny.

Kontrawariancja parametrów funkcji przy strukturalnej zgodności typescript
type Handler = (e: { type: string }) => void;

const onClick: Handler = (e: { type: string; x: number; y: number }) => {
  console.log(e.x, e.y);
};
// Błąd w strict mode: parametr Handler nie gwarantuje pól x/y
  • Struktura decyduje o kompatybilności, nie nazwa typu ani hierarchia dziedziczenia.
  • Excess property checking działa tylko na świeżych literałach obiektowych, nie na zmiennych.
  • Type inference redukuje szum, ale w publicznym API funkcji warto adnotować jawnie dla stabilności kontraktu.
  • Strukturalna zgodność funkcji wymaga zgodności parametrów i typu zwracanego, nie tylko nazwy sygnatury.
Mnemonik

TypeScript pyta 'czy wygląda jak kaczka i kwacze jak kaczka', nie 'czy nazywa się Kaczka'.

Dalej: 02 any, unknown i never: trzy skrajne typy

any, unknown i never: trzy skrajne typy

na egzaminie

any, unknown i never leżą na przeciwległych krańcach systemu typów. Typ any wyłącza kontrolę typów. Typ unknown wymusza jej odzyskanie przed użyciem. Typ never reprezentuje wartość, która nigdy nie powstanie.

Termin kluczowy

any: Ucieczkowy typ wyłączający type checking dla danej wartości. Przypisywalny do/z dowolnego typu bez żadnej walidacji. Używać wyłącznie jako świadomy wyjątek (np. brzeg z nietypowaną biblioteką JS), nigdy jako domyślne rozwiązanie problemu z typami.

Termin kluczowy

unknown: Type-safe odpowiednik any: przyjmuje dowolną wartość, ale nie pozwala na żadną operację (właściwości, wywołanie, indeksowanie) bez wcześniejszego zawężenia typu (type guard, instanceof, typeof).

Termin kluczowy

never: Typ pustego zbioru wartości, brak żadnej możliwej wartości. Zwracany przez funkcje, które zawsze rzucają wyjątek lub zapętlają się w nieskończoność; reprezentuje też 'niemożliwą' gałąź w exhaustiveness checking.

unknown wymusza zawężenie przed użyciem typescript
function parseConfig(raw: unknown): string {
  if (typeof raw !== 'string') {
    throw new TypeError('config must be a string');
  }
  return raw.toUpperCase(); // OK, raw zawężony do string
}

function unsafeParse(raw: any): string {
  return raw.toUpperCase(); // kompiluje się, ale crashuje w runtime dla raw = 42
}
never jako sygnał nieosiągalnego kodu typescript
function assertUnreachable(x: never): never {
  throw new Error(`Unexpected value: ${JSON.stringify(x)}`);
}

function fail(message: string): never {
  throw new Error(message);
}

// zmienna typu never akceptuje tylko wartości typu never
function process(input: string | number) {
  if (typeof input === 'string') return input.length;
  if (typeof input === 'number') return input * 2;
  return assertUnreachable(input); // input tutaj ma typ never, bo obie gałęzie wyczerpane
}
TypPrzyjmuje dowolną wartość?Operacje bez zawężeniaTypowy use case
anyTakWszystkie, bez kontroliMigracja z JS, biblioteki bez typów (unikać docelowo)
unknownTakBrak, wymaga narrowingDane z zewnątrz: JSON.parse, odpowiedzi API, argumenty catch
neverNie (zbiór pusty)Brak, nic nie da się przypisać do niczego innego niż neverExhaustiveness checking, funkcje zawsze rzucające
Pułapka

catch (e) w TS 4.4+ domyślnie typuje 'e' jako unknown (przy useUnknownInCatchVariables), nie any. Kod zakładający 'e.message' bez zawężenia (np. e instanceof Error) nie skompiluje się w strict setupie. To słuszne, bo w JS można rzucić cokolwiek, nie tylko Error.

Zero-cost abstraction

any i unknown nie generują żadnego kodu runtime: to czysto kompilacyjne konstrukty. Typ never analogicznie znika po kompilacji do JS, ale wpływa na to, czy compiler pozwoli zbudować kod.

Dalej: 03 Zawężanie typów i user-defined type guards

Zawężanie typów i user-defined type guards

na egzaminie

Narrowing to proces, w którym compiler zawęża szeroki typ (unię) do węższego wewnątrz konkretnej gałęzi kodu. Opiera się na kontrolnym przepływie: typeof, instanceof, in, porównaniach równości lub własnych predykatach.

Termin kluczowy

Type guard: Wyrażenie, które compiler rozpoznaje jako dowód zawężenia typu w danym bloku kodu. Wbudowane: typeof, instanceof, in, Array.isArray, porównania z null/undefined.

Termin kluczowy

User-defined type guard (predicate function): Funkcja zwracająca 'parameterName is Type' zamiast boolean. Informuje compiler, że pozytywny wynik funkcji zawęża typ argumentu do wskazanego typu we wszystkich miejscach wywołania.

Wbudowane narrowing: typeof, in, instanceof typescript
type Input = string | number | Date;

function format(value: Input): string {
  if (typeof value === 'string') return value.trim();
  if (typeof value === 'number') return value.toFixed(2);
  return value.toISOString(); // zostaje tylko Date
}

interface Dog { bark(): void }
interface Cat { meow(): void }

function speak(animal: Dog | Cat) {
  if ('bark' in animal) {
    animal.bark(); // zawężone do Dog
  } else {
    animal.meow(); // zawężone do Cat
  }
}
User-defined type guard z predykatem 'is' typescript
interface ApiError {
  code: number;
  message: string;
}

function isApiError(value: unknown): value is ApiError {
  return (
    typeof value === 'object' &&
    value !== null &&
    'code' in value &&
    'message' in value
  );
}

async function handle(response: unknown) {
  if (isApiError(response)) {
    console.error(response.code, response.message); // zawężone do ApiError
    return;
  }
  console.log('ok', response);
}
asserts is (3.7+)

Poza 'value is Type' istnieje wariant asercyjny 'function assertIsString(x: unknown): asserts x is string'. Zamiast zwracać boolean, funkcja rzuca wyjątek gdy warunek nie zachodzi, a compiler zawęża typ po wywołaniu, nie w gałęzi if.

Assertion function zawężająca po wywołaniu, nie w if typescript
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
  if (value === null || value === undefined) {
    throw new Error('Value must be defined');
  }
}

function load(id: string | undefined) {
  assertIsDefined(id);
  return id.toUpperCase(); // id zawężone do string po tej linii
}
Pułapka

Type guard z błędną implementacją kłamie compilerowi, np. gdy sprawdza tylko 'code' in value, a nie typ message. TS ufa deklarowanemu predykatowi bez weryfikacji jego ciała. Błąd w guardzie nie daje żadnego błędu kompilacji, tylko crash w runtime później.

  1. Zdefiniuj predykat 'x is Type' zamiast zwracać boolean, gdy wynik ma zawężać typ dla wywołującego.
  2. Sprawdź każde pole używane dalej, guard musi realnie gwarantować kształt, nie tylko część.
  3. Preferuj discriminated union nad ręcznym type guard, gdy to możliwe, compiler zawęzi automatycznie po polu-discriminancie.
Dalej: 04 Discriminated unions i exhaustiveness checking

Discriminated unions i exhaustiveness checking

na egzaminie

Discriminated union (tagged union) to wzorzec, w którym każdy człon unii ma wspólne pole-literal (discriminant). Dzięki niemu compiler automatycznie zawęża typ w switch/if, bez ręcznych type guardów.

Termin kluczowy

Discriminant property: Pole obecne w każdym wariancie unii, o typie unikalnego literału (string, number lub boolean literal), używane jako 'tag' identyfikujący konkretny wariant.

Discriminated union z polem 'kind' typescript
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default: {
      const exhaustiveCheck: never = shape;
      throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
    }
  }
}
Exhaustiveness checking

Gałąź 'default' przypisująca 'shape' do zmiennej typu never kompiluje się tylko wtedy, gdy wszystkie warianty unii są obsłużone. Nowy wariant Shape bez obsługi w switch daje błąd kompilacji w tej linii: to najsilniejsza statyczna gwarancja kompletności.

Efekt dodania nowego wariantu: błąd kompilacji w miejscu exhaustiveness check typescript
type Shape2 = Shape | { kind: 'square'; side: number };

function area2(shape: Shape2): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'rectangle': return shape.width * shape.height;
    case 'triangle': return (shape.base * shape.height) / 2;
    default: {
      // Error: Type '{ kind: "square"; side: number }' is not assignable to type 'never'
      const exhaustiveCheck: never = shape;
      throw new Error(String(exhaustiveCheck));
    }
  }
}
Pułapka

Bez pola-discriminanta narrowing bywa niepełny lub wymaga ręcznych 'in' checks. Dotyczy to np. dwóch wariantów rozróżnianych tylko obecnością różnych pól, bez wspólnego tagu. Znacznie łatwiej wtedy o pominiętą gałąź, której compiler nie wykryje jako błędu.

  • Zawsze dodawaj literal discriminant (np. 'type' albo 'kind') do wariantów unii reprezentujących stan/zdarzenie.
  • Kończ switch/if-chain gałęzią z assignmentem do never, to zamienia brak obsługi w błąd kompilacji, nie w bug produkcyjny.
  • Redux, state machines i event handling to naturalne miejsca dla discriminated unions.
Mnemonik

Discriminant + never w default = kompilator jako automatyczny reviewer kompletności switcha.

Dalej: 05 Generics: constraints, wnioskowanie, const type parameters

Generics: constraints, wnioskowanie, const type parameters

na egzaminie

Generyki parametryzują typy i funkcje bez utraty precyzji typowej, w przeciwieństwie do any, który tę precyzję zeruje. Constraints (extends) ograniczają, jakie typy wolno podstawić, keyof i indexed access pozwalają operować na kształcie obiektu w sposób type-safe.

Termin kluczowy

Generic constraint (extends): Ogranicza parametr typu do podzbioru typów spełniających daną strukturę, np. <T extends { id: string }> pozwala bezpiecznie odwołać się do 'id' wewnątrz funkcji.

Constraint z keyof dla bezpiecznego dostępu do właściwości typescript
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 'u1', name: 'Ada', age: 30 };
const name = getProperty(user, 'name'); // typ: string
// getProperty(user, 'email'); // Error: 'email' nie jest kluczem user
Termin kluczowy

const type parameter (TS 5.0+): Modyfikator 'const' na parametrze typu (<const T>) wymusza wnioskowanie literalnego (najwęższego) typu argumentu zamiast rozszerzonego. Nie trzeba ręcznie pisać 'as const' w miejscu wywołania.

const type parameter: literal inference bez 'as const' u wywołującego typescript
function tuple<const T extends readonly unknown[]>(...items: T): T {
  return items;
}

const a = tuple('a', 'b', 'c');
// bez 'const': typ [string, string, string] (tuple z widened typami, nie string[])
// z 'const': typ readonly ["a", "b", "c"]

declare function makeConfig<const T>(config: T): T;
const config = makeConfig({ mode: 'production', retries: 3 });
// config.mode: "production" (literal), nie string
Domyślne wartości parametrów typu

Podobnie jak parametry funkcji, parametry typu mogą mieć wartość domyślną: <T = string>. Jest używana, gdy caller nie poda argumentu typu i nie da się go wywnioskować z argumentów wartościowych.

Wiele parametrów typu z zależnościami między constraintami typescript
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

const merged = merge({ id: 1 }, { name: 'Ada' });
// typ merged: { id: number } & { name: string }
Pułapka

Nadużywanie generyków tam, gdzie wystarczyłby union type lub konkretny typ, to over-engineering. Jeśli funkcja realnie działa tylko na string | number, generyk <T> nie dodaje bezpieczeństwa. Daje tylko iluzję elastyczności i gorszy komunikat błędu.

  • extends ogranicza dozwolone typy, nie deklaruje dziedziczenia jak w klasach.
  • keyof T + T[K] to standardowy wzorzec bezpiecznego dostępu do właściwości generycznych.
  • const type parameter eliminuje potrzebę 'as const' w wywołaniu, gdy funkcja sama chce zachować literal types.
  • Generyk bez co najmniej dwóch miejsc użycia tego samego typu w sygnaturze zwykle nie jest potrzebny.
Dalej: 06 Typy warunkowe, infer i mapped types z key remapping

Typy warunkowe, infer i mapped types z key remapping

na egzaminie

Conditional types (T extends U ? X : Y) i mapped types ({ [K in keyof T]: ... }) to mechanizm meta-programowania na poziomie typów, pozwalający wyprowadzać nowe typy z istniejących zamiast pisać je ręcznie.

Termin kluczowy

Conditional type: Typ postaci 'T extends U ? X : Y', wybierający gałąź na podstawie relacji przypisywalności T do U. Rozdziela się (distributes) po unii, gdy T jest 'nagim' parametrem typu.

Termin kluczowy

infer: Słowo kluczowe używane wyłącznie wewnątrz warunku conditional type, deklarujące nową zmienną typu wywnioskowaną ze struktury sprawdzanego typu, np. wyciągnięcie typu zwracanego funkcji.

infer do wyciągania typu elementu z tablicy i zwracanej wartości funkcji typescript
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>; // string
type B = ElementType<number>;   // never

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type C = MyReturnType<() => Promise<number>>; // Promise<number>
Distributive conditional type po unii typescript
type ToArray<T> = T extends unknown ? T[] : never;
type D = ToArray<string | number>; // string[] | number[], nie (string | number)[]

// Zablokowanie dystrybucji: opakowanie T w krotkę wyłącza rozdzielanie po unii
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type E = ToArrayNonDist<string | number>; // (string | number)[]
Termin kluczowy

Mapped type: Typ generowany przez iterację po kluczach innego typu: '{ [K in keyof T]: Transform<T[K]> }'. Pozwala jednym wzorcem zdefiniować transformacje jak Readonly, Partial czy Record.

Termin kluczowy

Key remapping (klauzula 'as', TS 4.1+): W mapped type klauzula 'as' po 'K in keyof T' pozwala zmienić lub odfiltrować klucze wynikowego typu. Można np. dodać prefiks albo usunąć klucz, zwracając 'never' jako nowy klucz.

Key remapping: prefiksowanie i filtrowanie kluczy typescript
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person { name: string; age: number }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

// Filtrowanie: klucz zmapowany na 'never' znika z wynikowego typu
type OmitByValue<T, V> = {
  [K in keyof T as T[K] extends V ? never : K]: T[K];
};
type WithoutStrings = OmitByValue<{ a: string; b: number; c: boolean }, string>;
// { b: number; c: boolean }
Pułapka

infer wewnątrz warunku bez ograniczenia (bez extends na infer U) daje najszerszy możliwy typ, co bywa zbyt luźne. Dodanie 'infer U extends SomeBase' (TS 4.7+) zawęża typ już na etapie wnioskowania, nie dopiero po nim.

  • Conditional types rozdzielają się po unii tylko gdy sprawdzany typ to 'goły' parametr typu, nie opakowany w krotkę/obiekt.
  • infer działa wyłącznie w gałęzi 'extends' warunku, nie poza nim.
  • Klauzula 'as' w mapped type remapuje LUB usuwa klucze (zwrot 'never' = usunięcie).
  • To fundament większości utility types z lib.es5.d.ts i bibliotek typu Zod czy tRPC.
Dalej: 07 Wbudowane utility types i operator satisfies

Wbudowane utility types i operator satisfies

na egzaminie

TypeScript dostarcza zestaw generycznych utility types zbudowanych na mapped i conditional types (Partial, Pick, Omit, Record, ReturnType, Awaited). Operator 'satisfies' (od 4.9) łączy walidację typu z zachowaniem najwęższego wywnioskowanego typu.

Utility typeSygnatura (uproszczona)Zastosowanie
Partial<T>{ [K in keyof T]?: T[K] }Wszystkie pola opcjonalne, np. obiekt patch/update
Pick<T, K>{ [P in K]: T[P] }Wybór podzbioru pól istniejącego typu
Omit<T, K>Pick<T, Exclude<keyof T, K>>Typ bez wskazanych pól
Record<K, V>{ [P in K]: V }Mapa/słownik o znanych kluczach i jednolitym typie wartości
ReturnType<F>F extends (...a: any[]) => infer R ? R : neverWyciągnięcie typu zwracanego przez funkcję
Awaited<T>Rekurencyjnie odpakowuje Promise<Promise<...<T>>>Typ wartości po await, w tym zagnieżdżone Promise/thenable
Partial, Pick, Omit, Record w praktyce typescript
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

type UserPatch = Partial<Pick<User, 'name' | 'email'>>;
// { name?: string; email?: string }

type PublicUser = Omit<User, 'email'>;
// { id: string; name: string; role: 'admin' | 'user' }

type UsersByRole = Record<User['role'], User[]>;
// { admin: User[]; user: User[] }
ReturnType i Awaited na funkcji asynchronicznej typescript
async function fetchUser(id: string) {
  return { id, name: 'Ada' };
}

type FetchUserReturn = ReturnType<typeof fetchUser>; // Promise<{ id: string; name: string }>
type FetchUserResult = Awaited<FetchUserReturn>;     // { id: string; name: string }
Termin kluczowy

satisfies (TS 4.9+): Operator walidujący, że wyrażenie pasuje do wskazanego typu, bez zmiany wywnioskowanego typu tego wyrażenia na szerszy. W przeciwieństwie do adnotacji ': Type', satisfies zachowuje najwęższy (literalny) typ.

satisfies vs adnotacja typu: różnica w wywnioskowanym typie typescript
type Colors = Record<string, string | [number, number, number]>;

// Adnotacja: traci precyzję, 'red' ma typ string | [number,number,number]
const palette1: Colors = { red: [255, 0, 0], green: '#00ff00' };
// palette1.red.toUpperCase(); // Error: 'toUpperCase' nie istnieje na typie [number,number,number], wymaga zawężenia unii

// satisfies: waliduje kształt, ale zachowuje precyzyjny literal type
const palette2 = { red: [255, 0, 0], green: '#00ff00' } satisfies Colors;
// palette2.red ma typ [number, number, number] bez zawężania, dostępne np. palette2.red[0]
Pułapka

'as Type' to asercja: wyłącza sprawdzanie i mówi compilerowi 'zaufaj mi'. Nie waliduje niczego i pozwala błędnie rzutować niekompatybilne typy (z ograniczeniami).

Pułapka

'satisfies Type' faktycznie sprawdza zgodność i zgłasza błąd przy niezgodności, nie zmieniając wywnioskowanego typu. To różne narzędzia, nie synonimy.

  • Używaj satisfies, gdy chcesz walidację kształtu bez utraty precyzji literalnych typów (np. konfiguracje, palety, routing tables).
  • Adnotacja ': Type' jest właściwa, gdy chcesz, by zmienna miała dokładnie ten szerszy typ, np. w publicznym API.
  • Awaited jest rekurencyjny, bo Promise<Promise<T>> w JS spłaszcza się przez await do T, typ musi to odzwierciedlać.
Dalej: 08 Enumy, pułapki, strictNullChecks i template literal types

Enumy, pułapki, strictNullChecks i template literal types

Enumy to jedyna konstrukcja TS generująca realny kod JS o nietrywialnym runtime behaviour. Dlatego są źródłem częstych pułapek. strictNullChecks i template literal types dopełniają obraz nowoczesnego, precyzyjnego typowania.

Termin kluczowy

Numeric enum: Domyślny typ enum, kompilowany do obiektu z podwójnym mapowaniem (reverse mapping): nazwa→wartość i wartość→nazwa. Członkowie bez inicjalizatora auto-inkrementują się od 0.

Termin kluczowy

const enum: Enum całkowicie usuwany podczas kompilacji, wartości są inline'owane w miejscu użycia. Szybszy i bez runtime overhead.

Fakt na test

Zwykły const enum, zdefiniowany i używany w obrębie kompilowanego programu, działa poprawnie z --isolatedModules bez dodatkowej konfiguracji.

Fakt na test

Ograniczenie dotyczy wyłącznie ambient const enum, czyli deklarowanego bez implementacji, np. z zewnętrznego pliku .d.ts. Odwołanie się do niego pod --isolatedModules nadal daje błąd TS2748, niezależnie od wersji TypeScript.

Pułapka numeric enum: number-typed zmienna omija kontrolę typów typescript
enum Direction {
  Up,
  Down,
  Left,
  Right,
}

function move(dir: Direction) {
  console.log(dir);
}

move(0);    // OK, Direction.Up, ale brak semantycznej gwarancji
// move(99); // Error TS2345 od TS 5.0: literal poza zakresem enuma jest odrzucany

declare const n: number;
move(n);    // OK w compilerze! zmienna typu 'number' nadal omija kontrolę enuma
Pułapka

Od TS 5.0 (przebudowa 'all enums are union enums') kompilator odrzuca literał liczbowy spoza zdefiniowanych wartości, przekazany bezpośrednio do parametru typu enum. Zgłasza wtedy błąd TS2345. W TS 4.9 i starszych taki literal przechodził bez ostrzeżenia.

Pułapka

Realna pułapka, która przetrwała: zmienna typu zwykłego 'number' (nie literal) nadal jest swobodnie przypisywalna do typu enum bez żadnego zawężenia. String enum nie ma tego problemu, bo nie jest kompatybilny z 'string' w drugą stronę.

String enum lub union typu literałów jako bezpieczniejsza alternatywa typescript
type Direction2 = 'up' | 'down' | 'left' | 'right';

function move2(dir: Direction2) {
  console.log(dir);
}

move2('up');    // OK
// move2('diagonal'); // Error: nie jest dozwolonym literałem
// move2(0);          // Error: number nie jest Direction2
Rekomendacja społeczności i zespołu TS

Wiele zespołów (w tym część core teamu TS) rekomenduje dla nowego kodu union typów string-literal zamiast enum. Powody: brak runtime overhead, brak pułapek reverse-mapping, lepsza kompatybilność z --isolatedModules i narzędziami takimi jak esbuild/swc, transpilującymi plik po pliku.

Termin kluczowy

strictNullChecks: Flaga (włączona w 'strict': true) rozdzielająca null/undefined od pozostałych typów: 'string' nie przyjmuje null, trzeba jawnie napisać 'string | null'. Bez niej null/undefined są cichymi członkami każdego typu, co jest głównym źródłem 'Cannot read property of undefined' w runtime.

Template literal types: budowanie typów stringowych ze wzorca typescript
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `/api/${string}`;

type Endpoint = `${HttpMethod} ${ApiRoute}`;

const e1: Endpoint = 'GET /api/users';    // OK
// const e2: Endpoint = 'FETCH /api/users'; // Error: 'FETCH' nie jest HttpMethod

// Kombinacja z key remapping z sekcji 6: automatyczne generowanie nazw eventów
type EventName<T extends string> = `on${Capitalize<T>}Change`;
type ClickEvent = EventName<'click'>; // "onClickChange"
  • Preferuj string literal unions nad enum dla nowego kodu, chyba że potrzebujesz realnych wartości runtime i reverse mapping.
  • const enum eliminuje runtime overhead dla zwykłych enumów; ambient const enum pozostaje niekompatybilny z --isolatedModules (TS2748), niezależnie od wersji.
  • strictNullChecks powinno być włączone w każdym nowym projekcie, wyłączenie to prawie zawsze błąd konfiguracyjny, nie świadomy wybór.
  • Template literal types pozwalają walidować formaty stringów (route'y, nazwy CSS custom properties, eventy) na poziomie typów.
Dalej: 09 Standardowe dekoratory (5.0+) i module resolution bundler

Standardowe dekoratory (5.0+) i module resolution bundler

TypeScript 5.0 zastąpił eksperymentalne (legacy) dekoratory implementacją zgodną z finalizowaną propozycją TC39 Stage 3, dostępną bez flagi 'experimentalDecorators'. Ta sama wersja wprowadziła też tryb rozwiązywania modułów 'bundler' dopasowany do realnego zachowania Vite/esbuild/webpack.

Termin kluczowy

Standard decorators (TC39, stabilne w TS 5.0+): Nowa składnia dekoratorów zgodna ze standardem ECMAScript, niezależna od flagi kompilatora. Dekorator to funkcja przyjmująca dekorowaną wartość i obiekt 'context' opisujący jej kind, name i inne metadane, zwracająca opcjonalnie zamiennik.

Standard decorator na metodzie klasy (bez experimentalDecorators) typescript
function logCall(target: Function, context: ClassMethodDecoratorContext) {
  const methodName = String(context.name);
  return function (this: unknown, ...args: unknown[]) {
    console.log(`Calling ${methodName} with`, args);
    return target.apply(this, args);
  };
}

class PaymentService {
  @logCall
  charge(amount: number) {
    return amount * 1.0;
  }
}

new PaymentService().charge(100); // loguje wywołanie przed uruchomieniem metody
Pułapka

Legacy dekoratory (experimentalDecorators: true, używane m.in. przez starszy Angular/NestJS/TypeORM) mają inną sygnaturę i semantykę niż standard decorators z TS 5.0. Nie są binarnie kompatybilne, migracja wymaga świadomej zmiany, nie jest to automatyczne 'flip flagi'.

Zgodność frameworków (stan na moment wydania TS 5.x)

Wiele frameworków (Angular, część ekosystemu NestJS/TypeORM) w praktyce nadal opiera się na legacy decorators przez metadata reflection (emitDecoratorMetadata). Standard decorators formalnie nie definiują metadata reflection tak samo. Przed migracją sprawdź wymagania konkretnego frameworka w jego aktualnej dokumentacji.

Termin kluczowy

moduleResolution: bundler (TS 5.0+): Tryb rozwiązywania modułów naśladujący zachowanie nowoczesnych bundlerów (Vite, esbuild, webpack). Rozumie 'exports' field w package.json i nie wymusza rozszerzeń plików w importach ESM, w odróżnieniu od trybu 'node16'/'nodenext'.

tsconfig.json: konfiguracja typowa dla projektu budowanego przez Vite/bundler json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "verbatimModuleSyntax": true,
    "noEmit": true
  }
}
Pułapka

moduleResolution: 'bundler' zakłada, że finalny bundling robi zewnętrzne narzędzie (Vite/webpack/esbuild), a nie 'tsc'. Z nim JS emitowany przez tsc i uruchamiany w Node.js bez bundlera daje błędy resolution w runtime. Tryb nie służy do budowania paczek npm publikowanych bez bundlera.

  • Standard decorators nie wymagają już 'experimentalDecorators: true', są częścią samego języka od 5.0.
  • Sygnatura standard decoratora to (value, context), nie (target, propertyKey, descriptor) jak w legacy.
  • moduleResolution 'bundler' pasuje do projektów Vite/esbuild/webpack; 'node16'/'nodenext' pasuje do publikowanych paczek Node.js z natywnym ESM.
  • Framework-specific decorators (Angular, NestJS) mogą wciąż wymagać legacy trybu, sprawdź dokumentację frameworka przed migracją.

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: TypeScript

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.