Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik12 min czytania

TypeScript

TypeScript to typowany nadzbiór JavaScript od Microsoft. Poznaj typy, interfejsy, generyki i najlepsze praktyki pisania bezpiecznego kodu.

TypeScript - Kompletny przewodnik po języku, który zmienił JavaScript

Czym jest TypeScript i dlaczego stał się standardem?

TypeScript to język programowania stworzony przez Microsoft, który jest typowanym nadzbiorem JavaScript. Każdy kod JavaScript jest poprawnym kodem TypeScript, ale TypeScript dodaje opcjonalne statyczne typowanie, które pozwala wykrywać błędy w czasie kompilacji zamiast w runtime.

W 2025 roku TypeScript jest używany przez większość profesjonalnych projektów JavaScript, w tym Angular, Vue 3, Next.js, Prisma i tysiące innych bibliotek i aplikacji.

Dlaczego TypeScript?

Wykrywanie błędów przed uruchomieniem

Code
TypeScript
// JavaScript - błąd w runtime
function greet(name) {
  return 'Hello, ' + name.toUppercase() // typo - toUpperCase
}
greet('world') // Error w runtime!

// TypeScript - błąd w edytorze
function greet(name: string): string {
  return 'Hello, ' + name.toUppercase() // ❌ Error: Property 'toUppercase' does not exist
}

Autouzupełnianie i dokumentacja

Code
TypeScript
interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

function getUser(id: number): User {
  // ...
}

const user = getUser(1)
user. // Autouzupełnianie: id, name, email, role

Refaktoryzacja bez strachu

TypeScript sprawdza cały codebase i wskazuje wszystkie miejsca wymagające zmiany przy refaktoryzacji.

Podstawy typów

Typy prymitywne

Code
TypeScript
// Podstawowe typy
let name: string = 'Jan'
let age: number = 25
let isActive: boolean = true
let nothing: null = null
let notDefined: undefined = undefined

// Inferencja typów - TypeScript sam wyznacza typ
let city = 'Warszawa' // typ: string
let count = 42 // typ: number

// Tablice
let numbers: number[] = [1, 2, 3]
let names: Array<string> = ['Anna', 'Bartek']

// Tuple - tablica o stałej długości i typach
let person: [string, number] = ['Jan', 25]

// Enum
enum Status {
  Pending = 'pending',
  Active = 'active',
  Completed = 'completed'
}

let taskStatus: Status = Status.Active

Typy specjalne

Code
TypeScript
// any - wyłącza type checking (unikaj!)
let anything: any = 'string'
anything = 42 // OK, ale niebezpieczne

// unknown - bezpieczniejsza alternatywa dla any
let value: unknown = 'hello'
// value.toUpperCase() // ❌ Error - musi być sprawdzony
if (typeof value === 'string') {
  value.toUpperCase() // ✅ OK
}

// never - funkcja nigdy nie kończy się normalnie
function throwError(message: string): never {
  throw new Error(message)
}

function infiniteLoop(): never {
  while (true) {}
}

// void - funkcja nic nie zwraca
function logMessage(message: string): void {
  console.log(message)
}

Union i Intersection Types

Code
TypeScript
// Union - jeden z typów
type StringOrNumber = string | number

function printId(id: StringOrNumber) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id.toFixed(2))
  }
}

// Literal types
type Direction = 'north' | 'south' | 'east' | 'west'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

// Intersection - kombinacja typów
interface HasName {
  name: string
}

interface HasAge {
  age: number
}

type Person = HasName & HasAge

const person: Person = {
  name: 'Jan',
  age: 25
}

Interfejsy i Type Aliases

Interfejsy

Code
TypeScript
// Podstawowy interface
interface User {
  id: number
  name: string
  email: string
}

// Opcjonalne pola
interface Config {
  apiUrl: string
  timeout?: number // opcjonalne
  retry?: boolean
}

// Readonly
interface Point {
  readonly x: number
  readonly y: number
}

const p: Point = { x: 10, y: 20 }
// p.x = 5 // ❌ Error - readonly

// Rozszerzanie interfejsów
interface Animal {
  name: string
}

interface Dog extends Animal {
  breed: string
  bark(): void
}

// Interfejsy dla funkcji
interface SearchFunction {
  (query: string, limit?: number): Promise<Result[]>
}

const search: SearchFunction = async (query, limit = 10) => {
  // ...
}

// Interfejsy dla klas
interface Serializable {
  serialize(): string
  deserialize(data: string): void
}

class User implements Serializable {
  serialize(): string {
    return JSON.stringify(this)
  }

  deserialize(data: string): void {
    Object.assign(this, JSON.parse(data))
  }
}

Type Aliases

Code
TypeScript
// Type alias - alternatywa dla interface
type User = {
  id: number
  name: string
}

// Type alias dla prymitywów
type ID = string | number
type Nullable<T> = T | null

// Type alias dla funkcji
type EventHandler = (event: Event) => void

// Type alias dla unii
type Result<T> =
  | { success: true; data: T }
  | { success: false; error: string }

function fetchData(): Result<User[]> {
  try {
    const data = fetch('/api/users')
    return { success: true, data }
  } catch (err) {
    return { success: false, error: 'Failed to fetch' }
  }
}

Interface vs Type - kiedy co?

Code
TypeScript
// Interface - dla obiektów, można rozszerzać
interface Animal {
  name: string
}

interface Dog extends Animal {
  breed: string
}

// Interfejsy można "mergować"
interface User {
  name: string
}

interface User {
  age: number
}

// User ma teraz name i age

// Type - dla unii, tuple, prymitywów
type Status = 'pending' | 'active' | 'done'
type Coordinates = [number, number]
type Callback = () => void

// Type nie można rozszerzać (ale można intersection)
type Cat = Animal & { meow(): void }

Generyki

Podstawy generyków

Code
TypeScript
// Funkcja generyczna
function identity<T>(value: T): T {
  return value
}

const num = identity<number>(42) // typ: number
const str = identity('hello') // typ: string (inferencja)

// Generyczna tablica
function first<T>(array: T[]): T | undefined {
  return array[0]
}

const firstNumber = first([1, 2, 3]) // typ: number | undefined

// Generyczny interface
interface Box<T> {
  value: T
}

const stringBox: Box<string> = { value: 'hello' }
const numberBox: Box<number> = { value: 42 }

Ograniczenia generyków (Constraints)

Code
TypeScript
// Constraint - T musi mieć właściwość length
function getLength<T extends { length: number }>(item: T): number {
  return item.length
}

getLength('hello') // ✅ OK
getLength([1, 2, 3]) // ✅ OK
getLength({ length: 10 }) // ✅ OK
// getLength(42) // ❌ Error - number nie ma length

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: 'Jan', age: 25 }
const name = getProperty(user, 'name') // typ: string
const age = getProperty(user, 'age') // typ: number
// getProperty(user, 'invalid') // ❌ Error

Generyki z wartościami domyślnymi

Code
TypeScript
interface ApiResponse<T = unknown> {
  data: T
  status: number
  message?: string
}

// Bez podania typu - T = unknown
const response: ApiResponse = { data: 'test', status: 200 }

// Z podaniem typu
const userResponse: ApiResponse<User> = {
  data: { id: 1, name: 'Jan' },
  status: 200
}

Generyki w klasach

Code
TypeScript
class Queue<T> {
  private items: T[] = []

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

  dequeue(): T | undefined {
    return this.items.shift()
  }

  peek(): T | undefined {
    return this.items[0]
  }

  get length(): number {
    return this.items.length
  }
}

const numberQueue = new Queue<number>()
numberQueue.enqueue(1)
numberQueue.enqueue(2)
const first = numberQueue.dequeue() // typ: number | undefined

Utility Types

TypeScript dostarcza wbudowane typy pomocnicze:

Code
TypeScript
interface User {
  id: number
  name: string
  email: string
  age?: number
}

// Partial - wszystkie pola opcjonalne
type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string; age?: number }

// Required - wszystkie pola wymagane
type RequiredUser = Required<User>
// { id: number; name: string; email: string; age: number }

// Readonly - wszystkie pola tylko do odczytu
type ReadonlyUser = Readonly<User>

// Pick - wybierz tylko niektóre pola
type UserName = Pick<User, 'id' | 'name'>
// { id: number; name: string }

// Omit - pomiń niektóre pola
type UserWithoutEmail = Omit<User, 'email'>
// { id: number; name: string; age?: number }

// Record - słownik
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>
const roles: UserRoles = {
  'jan@example.com': 'admin',
  'anna@example.com': 'user'
}

// Extract - wybierz z unii
type Numbers = Extract<string | number | boolean, number>
// number

// Exclude - wyklucz z unii
type StringOrBoolean = Exclude<string | number | boolean, number>
// string | boolean

// NonNullable - usuń null i undefined
type MaybeString = string | null | undefined
type DefinitelyString = NonNullable<MaybeString>
// string

// ReturnType - typ zwracany przez funkcję
function createUser() {
  return { id: 1, name: 'Jan' }
}
type UserType = ReturnType<typeof createUser>
// { id: number; name: string }

// Parameters - typy parametrów funkcji
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>
// [string, number]

Type Guards

Code
TypeScript
// typeof guard
function processValue(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase()
  }
  return value.toFixed(2)
}

// instanceof guard
class Dog {
  bark() { console.log('Woof!') }
}

class Cat {
  meow() { console.log('Meow!') }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark()
  } else {
    animal.meow()
  }
}

// in guard
interface Bird {
  fly(): void
  layEggs(): void
}

interface Fish {
  swim(): void
  layEggs(): void
}

function move(animal: Bird | Fish) {
  if ('fly' in animal) {
    animal.fly()
  } else {
    animal.swim()
  }
}

// Custom type guard
interface Admin {
  role: 'admin'
  permissions: string[]
}

interface User {
  role: 'user'
  email: string
}

function isAdmin(person: Admin | User): person is Admin {
  return person.role === 'admin'
}

function getAccess(person: Admin | User) {
  if (isAdmin(person)) {
    return person.permissions // TypeScript wie że to Admin
  }
  return ['read'] // TypeScript wie że to User
}

Zaawansowane typy

Mapped Types

Code
TypeScript
// Tworzenie nowych typów przez mapowanie
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Optional<T> = {
  [P in keyof T]?: T[P]
}

type Nullable<T> = {
  [P in keyof T]: T[P] | null
}

// Przykład użycia
interface User {
  id: number
  name: string
}

type ReadonlyUser = Readonly<User>
// { readonly id: number; readonly name: string }

Conditional Types

Code
TypeScript
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false

type A = IsString<string> // true
type B = IsString<number> // false

// Bardziej praktyczny przykład
type ArrayElement<T> = T extends (infer U)[] ? U : never

type StringArray = string[]
type Element = ArrayElement<StringArray> // string

// Unwrap Promise
type Awaited<T> = T extends Promise<infer U> ? U : T

type PromiseString = Promise<string>
type JustString = Awaited<PromiseString> // string

Template Literal Types

Code
TypeScript
// Typy oparte na string templates
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

// Praktyczne użycie z API
type HttpMethod = 'get' | 'post' | 'put' | 'delete'
type ApiEndpoint = `/api/${string}`
type ApiRoute = `${Uppercase<HttpMethod>} ${ApiEndpoint}`

const route: ApiRoute = 'GET /api/users' // ✅
// const invalid: ApiRoute = 'PATCH /api/users' // ❌

TypeScript z React

Komponenty funkcyjne

Code
TypeScript
// Props interface
interface ButtonProps {
  children: React.ReactNode
  onClick?: () => void
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

// Funkcyjny komponent
function Button({ children, onClick, variant = 'primary', disabled }: ButtonProps) {
  return (
    <button
      className={`btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  )
}

// Lub z React.FC (mniej zalecane)
const Button: React.FC<ButtonProps> = ({ children, onClick }) => {
  return <button onClick={onClick}>{children}</button>
}

Hooks z TypeScript

Code
TypeScript
// useState
const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)

// useRef
const inputRef = useRef<HTMLInputElement>(null)
const timerRef = useRef<number | null>(null)

// useReducer
interface State {
  count: number
  error: string | null
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'error'; payload: string }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 }
    case 'decrement':
      return { ...state, count: state.count - 1 }
    case 'error':
      return { ...state, error: action.payload }
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, error: null })

Event handlers

Code
TypeScript
function Form() {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value)
  }

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
  }

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log(e.clientX, e.clientY)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
      <button onClick={handleClick}>Submit</button>
    </form>
  )
}

Konfiguracja - tsconfig.json

Code
JSON
{
  "compilerOptions": {
    // Wersja docelowa JavaScript
    "target": "ES2022",

    // System modułów
    "module": "ESNext",
    "moduleResolution": "bundler",

    // Ścisłe sprawdzanie typów (zalecane!)
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,

    // Ścieżki
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },

    // JSX dla React
    "jsx": "react-jsx",

    // Inne przydatne opcje
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Best Practices

Do's

Code
TypeScript
// ✅ Używaj strict mode
"strict": true

// ✅ Definiuj typy dla API responses
interface ApiResponse<T> {
  data: T
  status: number
}

// ✅ Używaj unknown zamiast any
function parseJSON(json: string): unknown {
  return JSON.parse(json)
}

// ✅ Używaj const assertions
const config = {
  api: '/api',
  timeout: 5000
} as const

// ✅ Eksportuj typy razem z funkcjami
export interface User { /* ... */ }
export function getUser(): User { /* ... */ }

Don'ts

Code
TypeScript
// ❌ Unikaj any
function process(data: any) { } // Zła praktyka

// ❌ Nie ignoruj błędów TypeScript
// @ts-ignore
const x = badCode() // Zła praktyka

// ❌ Nie używaj ! bez potrzeby
const el = document.getElementById('app')!
// Lepiej: sprawdź czy istnieje

// ❌ Nie nadużywaj type assertions
const user = data as User // Preferuj type guards

FAQ

Czy TypeScript spowalnia development?

Początkowo tak, ale długoterminowo przyspiesza przez:

  • Wykrywanie błędów wcześniej
  • Lepsze autouzupełnianie
  • Łatwiejszą refaktoryzację

Czy muszę typować wszystko?

Nie. TypeScript ma świetną inferencję typów. Typuj interfejsy publiczne, a wewnętrzny kod często nie wymaga explicit typów.

TypeScript vs JSDoc?

TypeScript oferuje znacznie więcej: generyki, utility types, conditional types. JSDoc jest OK dla prostych projektów.

Podsumowanie

TypeScript to must-have dla profesjonalnych projektów JavaScript:

  • Type safety - Błędy wykryte w czasie kompilacji
  • DX - Świetne autouzupełnianie i dokumentacja
  • Refaktoryzacja - Bez strachu o regresje
  • Ekosystem - Obsługiwany przez wszystkie major frameworks