EN

TypeScript

Referenční přehled TypeScriptu: nastavení projektu, typový systém, generika, kompilátor tsc, moduly, dekorátory, zpracování chyb a testování v Jestu.

Co tato dovednost pokrývá

Praktický referenční přehled vývoje v TypeScriptu: nastavení projektu a tsconfig.json, základní i pokročilý typový systém (interfacy, generika, uniony, utility types), CLI kompilátoru tsc, modulové systémy, dekorátory, zpracování chyb, testování s Jestem a osvědčené postupy pro strukturu projektu.

Kdy ji použít

  • Zakládání nového TypeScript projektu nebo psaní tsconfig.json od nuly
  • Rozhodování mezi interfacem, type aliasem nebo generikem pro daný kus kódu
  • Vyhledávání přepínačů kompilátoru tsc — watch mode, strict mode, cílový výstup/modul, deklarační soubory
  • Konfigurace ES modulů vs. CommonJS interoperability v TypeScript projektu
  • Práce s type guards, typovými asercemi nebo utility types jako Partial, Pick, Omit
  • Psaní vlastních dekorátorů pro třídy nebo metody
  • Nastavení Jestu s ts-jest pro testování TypeScript kódu
  • Strukturování TypeScript projektu, konvence pojmenování nebo konfigurace ESLintu/Prettieru

Začínáme s TypeScriptem

Instalace a nastavení

Globální instalace:

npm install -g typescript

Nastavení projektu:

# Inicializace npm projektu
npm init -y

# Lokální instalace TypeScriptu
npm install --save-dev typescript

# Inicializace konfigurace TypeScriptu
npx tsc --init

Základní tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Základy TypeScriptu

Základní typy

Primitivní typy:

// String, number, boolean
let name: string = "John";
let age: number = 30;
let isStudent: boolean = false;

// Typy polí
let numbers: number[] = [1, 2, 3, 4];
let names: Array<string> = ["Alice", "Bob"];

// Tuple (pole pevné délky)
let person: [string, number] = ["John", 30];

// Enum
enum Color {
  Red,
  Green,
  Blue
}
let color: Color = Color.Red;

// Any (vyhýbat se, pokud možno)
let anything: any = "could be anything";

// Void (pro funkce bez návratové hodnoty)
function logMessage(): void {
  console.log("Hello!");
}

// Never (pro funkce, které nikdy nevrátí hodnotu)
function throwError(message: string): never {
  throw new Error(message);
}

Interfacy a typové aliasy

Interfacy:

interface User {
  id: number;
  name: string;
  email: string;
  isActive?: boolean;  // Volitelná vlastnost
  readonly createdAt: Date;  // Vlastnost pouze pro čtení
}

// Rozšíření interfaců
interface Admin extends User {
  role: string;
  permissions: string[];
}

// Interface pro funkce
interface Comparator<T> {
  (a: T, b: T): number;
}

// Interface pro indexovatelné typy
interface Dictionary<T> {
  [key: string]: T;
}

Typové aliasy:

// Jednoduchý typový alias
type UserId = number;

// Union typy
type Status = "active" | "inactive" | "pending";

// Průnikové (intersection) typy
type AdminUser = User & { role: string };

// Generický typový alias
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

Třídy

Základní třída:

class Person {
  // Vlastnosti
  public name: string;
  private age: number;
  protected email: string;

  // Konstruktor
  constructor(name: string, age: number, email: string) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  // Metody
  public greet(): string {
    return `Hello, my name is ${this.name}`;
  }

  // Getter
  get fullInfo(): string {
    return `${this.name} (${this.age} years old)`;
  }

  // Setter
  set updateEmail(newEmail: string) {
    this.email = newEmail;
  }
}

// Použití
const person = new Person("John", 30, "john@example.com");
console.log(person.greet());

Dědičnost:

class Employee extends Person {
  private salary: number;

  constructor(name: string, age: number, email: string, salary: number) {
    super(name, age, email);
    this.salary = salary;
  }

  // Přepsání metody
  greet(): string {
    return `Hello, I'm ${this.name} and I work here.`;
  }

  // Nová metoda
  getSalary(): number {
    return this.salary;
  }
}

Abstraktní třídy:

abstract class Shape {
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // Konkrétní metoda
  describe(): string {
    return `Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`;
  }
}

class Rectangle extends Shape {
  constructor(private width: number, private height: number) {
    super();
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

Generika

Generické funkce:

function identity<T>(arg: T): T {
  return arg;
}

// Použití
let output1 = identity<string>("Hello");
let output2 = identity<number>(42);

// Více typových parametrů
function combine<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

Generické třídy:

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

// Použití
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

Generická omezení (constraints):

interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// Použití
logLength("Hello");      // OK
logLength([1, 2, 3]);    // OK
logLength(42);           // Chyba: number nemá vlastnost length

Pokročilé funkce TypeScriptu

Union a intersection typy

Union typy:

type StringOrNumber = string | number;

function formatValue(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}

// Diskriminované uniony
type SuccessResponse = { success: true; data: any };
type ErrorResponse = { success: false; error: string };
type ApiResponse = SuccessResponse | ErrorResponse;

function handleResponse(response: ApiResponse) {
  if (response.success) {
    console.log("Data:", response.data);
  } else {
    console.error("Error:", response.error);
  }
}

Intersection typy:

type Timestamped = { timestamp: Date };
type Named = { name: string };

type TimestampedNamed = Timestamped & Named;

// Ekvivalentní:
// type TimestampedNamed = {
//   timestamp: Date;
//   name: string;
// }

Type guards a asercí

Type guards:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

function processValue(value: unknown) {
  if (isString(value)) {
    console.log("String length:", value.length);
  } else if (isNumber(value)) {
    console.log("Number squared:", value * value);
  }
}

Typové asercí:

// Syntaxe s lomenými závorkami
let someValue: unknown = "hello world";
let strLength: number = (<string>someValue).length;

// Syntaxe as (doporučeno)
let strLength2: number = (someValue as string).length;

// Non-null asercí
function process(user: User | null) {
  console.log(user!.name);  // Tvrdí, že user není null
}

Utility types

Vestavěné utility types:

interface Todo {
  id: number;
  title: string;
  completed: boolean;
  createdAt: Date;
}

// Partial - všechny vlastnosti volitelné
type PartialTodo = Partial<Todo>;

// Required - všechny vlastnosti povinné
type RequiredTodo = Required<Todo>;

// Pick - vybere konkrétní vlastnosti
type TodoPreview = Pick<Todo, "id" | "title">;

// Omit - odstraní konkrétní vlastnosti
type TodoWithoutId = Omit<Todo, "id">;

// Readonly - všechny vlastnosti pouze pro čtení
type ReadonlyTodo = Readonly<Todo>;

Vlastní utility types:

// Extrakce návratového typu funkce
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;

// Extrakce typu, na který se rozřeší promise
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;

// Konkrétní vlastnosti nastavené jako volitelné
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

Příkazy kompilátoru TypeScriptu (tsc)

Základní kompilace

Kompilace všech souborů:

npx tsc

Kompilace konkrétního souboru:

npx tsc file.ts

Inicializace TypeScript projektu:

npx tsc --init

Watch mode a výstup

Watch mode (průběžná kompilace):

npx tsc --watch
npx tsc -w

Určení výstupního adresáře:

npx tsc --outDir ./dist

Určení kořenového adresáře:

npx tsc --rootDir ./src

Možnosti kompilace

Cílová verze ECMAScriptu:

npx tsc --target ES2020
npx tsc --target ES5

Modulový systém:

npx tsc --module commonjs
npx tsc --module es2020

Zahrnutí/vyloučení souborů:

npx tsc --include src/**/*.ts
npx tsc --exclude node_modules

Kontrola typů a diagnostika

Přísná kontrola typů:

npx tsc --strict

Kontrola typů bez generování souborů:

npx tsc --noEmit

Generování deklaračních souborů:

npx tsc --declaration

Generování source maps:

npx tsc --sourceMap

Konfigurace projektu

Použití konkrétního tsconfig.json:

npx tsc --project ./path/to/tsconfig.json
npx tsc -p ./path/to/tsconfig.json

Inkrementální kompilace:

npx tsc --incremental

Odstranění komentářů z výstupu:

npx tsc --removeComments

Modulové systémy

ES6 moduly

Export:

// Pojmenované exporty
export const PI = 3.14159;
export function calculateArea(radius: number): number {
  return PI * radius * radius;
}

// Výchozí export
export default class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}

Import:

// Pojmenované importy
import { PI, calculateArea } from './math';

// Výchozí import
import Calculator from './calculator';

// Přejmenování importů
import { calculateArea as area } from './math';

// Import všeho
import * as MathUtils from './math';

CommonJS (Node.js)

Export:

// Jediný export
export = function() {
  return "Hello";
};

// Nebo pomocí module.exports
const utils = {
  add: (a: number, b: number) => a + b,
  multiply: (a: number, b: number) => a * b
};

export = utils;

Import:

// Import CommonJS modulu
import utils = require('./utils');

Dekorátory

Níže uvedená legacy syntaxe dekorátorů vyžaduje "experimentalDecorators": true v tsconfig.json (v základní konfiguraci výše není nastaveno). TypeScript 5+ navíc nabízí samostatnou, standardizovanou syntaxi dekorátorů s mírně odlišnou sémantikou — před použitím těchto příkladů ověřte, kterou variantu projekt cílí.

Dekorátory tříd:

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

Dekorátory metod:

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

Zpracování chyb a ladění

Typy chyb

Vlastní třídy chyb:

class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

class NetworkError extends Error {
  constructor(message: string, public statusCode: number) {
    super(message);
    this.name = 'NetworkError';
  }
}

Vzory pro zpracování chyb:

async function fetchData(url: string): Promise<any> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new NetworkError('Network request failed', response.status);
    }
    return await response.json();
  } catch (error) {
    if (error instanceof NetworkError) {
      console.error(`Network error ${error.statusCode}: ${error.message}`);
    } else if (error instanceof ValidationError) {
      console.error(`Validation error for ${error.field}: ${error.message}`);
    } else {
      console.error('Unknown error:', error);
    }
    throw error;
  }
}

Testování v TypeScriptu

Použití Jestu

Nastavení:

npm install --save-dev jest @types/jest ts-jest
npx ts-jest config:init

Základní test:

// sum.ts
export function sum(a: number, b: number): number {
  return a + b;
}

// sum.test.ts
import { sum } from './sum';

describe('sum', () => {
  it('should add two numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('should handle negative numbers', () => {
    expect(sum(-1, 1)).toBe(0);
  });
});

Konfigurace testů (jest.config.js)

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
  ],
};

Osvědčené postupy

Organizace kódu

  1. Struktura souborů:

    src/
    ├── types/
    ├── interfaces/
    ├── utils/
    ├── services/
    ├── components/
    └── index.ts
    
  2. Konvence pojmenování:

    • PascalCase pro třídy, interfacy a typové aliasy
    • camelCase pro proměnné, funkce a vlastnosti
    • UPPER_CASE pro konstanty
  3. Typová bezpečnost:

    • Vyhýbat se typu any, pokud je to možné
    • Používat strict mode v tsconfig.json
    • Využívat union typy pro lepší typovou bezpečnost

Výkonnostní ohledy

  1. Tree shaking: Používat ES6 moduly pro lepší bundling
  2. Importy pouze typů: Import typů bez dopadu na runtime
    import type { User } from './types';
    
  3. Deklarační soubory: Generovat .d.ts soubory pro knihovny

Nástroje a vývoj

Konfigurace ESLintu:

{
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

Konfigurace Prettieru:

{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

Studijní zdroje